在入门篇,我们提到了UIAutomator2.0与1.0的不同。其中,2.0基于JUnit,使用Annotation声明测试案例,是一个重要的特点。本篇将通过实例来讲述几个常用Annotation的使用。
不论何种测试,都需要初始化平台环境,遍历测试案例,为每个测试案例初始化测试上下文,并按照一定的顺序执行测试。
Annotation也是一样的思路。
(1)@BeforeClass:用于声明方法,该方法将在测试案例集(class)实例创建前执行。因此,该方法必须为static类型,仅被执行一次。相当于初始化测试平台环境。
(2)@AfterClass:用于声明方法,该方法将在测试案例集合(class)实例销毁前执行。因此,该方法也必须为static类型,仅被执行一次,相当于清理测试平台环境。
(3)@Before:用于声明方法,该方法将在每个测试案例(function)被调用前执行,因此,该方法可根据测试案例数量,被多次执行,相当于初始化测试上下文。
(4)@After:用于声明方法,该方法将在每个测试案例(function)被调用后执行,因此,该方法可根据测试案例数量,被多次执行,相当于清理测试上下文。
(5)@Test:用于声明方法,该方法将被视为测试案例(function)被执行。
(6)@Ignore:用于声明方法,表示忽略该测试案例。
我们来看一个完整简单案例
RunWith(AndroidJUnit4.class)
public class SalaryShowAppTest {
private static final String TAG="APPUITest";
private String mPackageName="com.breakloop.salaryshow";
private String mLaunchActivityName=".MainActivity";
public static UiDevice mDevice;
@BeforeClass
public static void init(){
Log.i(TAG, "init ");
mDevice=UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}
@Before
public void OpenAPP(){
Log.i(TAG, "OpenAPP ");
try {
if(!mDevice.isScreenOn()){ //唤醒屏幕
mDevice.wakeUp();
}
} catch (RemoteException e) {
e.printStackTrace();
}
Utils.startAPP(mPackageName); //启动app
mDevice.waitForWindowUpdate(mPackageName, 2 * 2000);
}
@Test
public void case1(){
Log.i(TAG, "case1 ");
}
@Test
public void case3(){
Log.i(TAG, "case3 ");
}
@Test
public void case20(){
Log.i(TAG, "case20 ");
}
@Test
public void case21(){
Log.i(TAG, "case21 ");
}
@Test@Ignore
public void case5(){
Log.i(TAG, "case5 ");
}
@After
public void closeAPP(){
Log.i(TAG, "closeAPP ");
Utils.closeAPP(mDevice,mPackageName);
}
@AfterClass
public static void destroy(){
Log.i(TAG, "destroy ");
mDevice=null;
}
}
Utils文件内容为
public class Utils {
public static void startAPP(String sPackageName){
Context mContext = InstrumentationRegistry.getContext();
Intent myIntent = mContext.getPackageManager().getLaunchIntentForPackage(sPackageName); //启动app
mContext.startActivity(myIntent);
}
public static void closeAPP(UiDevice uiDevice, String sPackageName){
try {
uiDevice.executeShellCommand("am force-stop "+sPackageName);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void startAPP(UiDevice uiDevice,String sPackageName, String sLaunchActivity){
try {
uiDevice.executeShellCommand("am start -n "+sPackageName+"/"+sLaunchActivity);
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们运行测试案例集合,并查看日志
I/APPUITest: init
I/APPUITest: OpenAPP
I/APPUITest: case20
I/APPUITest: closeAPP
I/APPUITest: OpenAPP
I/APPUITest: case21
I/APPUITest: closeAPP
I/APPUITest: OpenAPP
I/APPUITest: case1
I/APPUITest: closeAPP
I/APPUITest: OpenAPP
I/APPUITest: case3
I/APPUITest: closeAPP
I/APPUITest: destroy
看完LOG,那么问题来了~
Test Case的执行顺序是如何决定的?
默认情况下,是按照Test Case名称的HASH值进行排序。若HASH值相同,则以字母字典顺序执行。
当然Test Case的执行顺序,也可使用Annotation来指定。
(7)@FixMethodOrder(order):用于修饰测试案例集合类(class)。order有三种取值:MethodSorters.DEFAULT(默认方式),MethodSorters.NAME_ASCENDING(字母字典方式),MethodSorters.JVM(由JVM自己决定)。
这里我们看一下字母字典方式执行顺序。
I/APPUITest: init
I/APPUITest: OpenAPP
I/APPUITest: case1
I/APPUITest: closeAPP
I/APPUITest: OpenAPP
I/APPUITest: case20
I/APPUITest: closeAPP
I/APPUITest: OpenAPP
I/APPUITest: case21
I/APPUITest: closeAPP
I/APPUITest: OpenAPP
I/APPUITest: case3
I/APPUITest: closeAPP
I/APPUITest: destroy
JVM顺序方式,存在一定的随机性,通常不做考虑。
在本地做了几次JVM顺序的执行,发现每次的执行顺序都与NAME_ASCENDING一致。可能不同的JVM不同,或者存在其他影响因素,这里不再深究。有知晓的同学,还望指点。
我们来做一下扩展~
(8)@Test(timeout=XXX):用于声明方法,该方法必须在XXX毫秒内完成,否则视为失败测试案例。
我们对Test Case1进行修改
@Test(timeout = 1)
public void case1(){
Log.i(TAG, "case1: ");
for (int index=1;index<100;index++){
Log.i(TAG, "index="+index);
}
}
运行,并查看LOG和结果
I/APPUITest: init
I/APPUITest: OpenAPP
I/APPUITest: case1
I/APPUITest: index=1
...
I/APPUITest: index=99
I/APPUITest: closeAPP
I/APPUITest: destroy:
org.junit.runners.model.TestTimedOutException: test timed out after 1 milliseconds
at android.util.Log.println_native(Native Method)
at android.util.Log.i(Log.java:180)
...
Tests ran to completion.
可见,IDE是将测试案例完全执行完毕后,再与Timeout进行比对,判断测试案例是否成功。
当然,我们可以通过,修改timeout(Long型),或减少任务量等方式,将Test Case成功执行。这里不再啰嗦。
(9)@Test(expected=XXException.class):用于声明方法,该方法应当抛出XXException异常,若未抛异常或抛出其他异常,则视为失败测试案例。
我们队Test Case3进行修改
@Test(expected = IOException.class)
public void case3(){
Log.i(TAG, "case3: ");
}
运行,并查看LOG和结果
I/APPUITest: init
I/APPUITest: OpenAPP
I/APPUITest: case3
I/APPUITest: closeAPP
I/APPUITest: destroy
java.lang.AssertionError: Expected exception: java.io.IOException
...
Tests ran to completion.
我们可以手动抛出异常,使得测试案例成功执行。
@Test(expected = IOException.class)
public void case3() throws IOException {
Log.i(TAG, "case3: ");
throw new IOException();
}
至此,常用的Annotation介绍完毕。若有遗漏,之后再做补充。