本文主要讲解 ATSL 测试框架
JUnit 3 vs Junit 4
AndroidJUnitRunner是一个可以用来运行JUnit 3和JUnit 4样式的测试类的Test Runner,并且同时支持Espresso和UI Automator。这是对于之前的InstrumentationTestRunner的一个升级,如果你去查看Gradle文档中对于Testing配置的说明,会发现推荐的Test Runner为InstrumentationTestRunner。InstrumentationTestRunner只支持JUnit 3样式的测试用例,而我们在写Android测试用例时应该尽可能使用JUnit 4样式来实现。
相对于Junit 3, JUnit 4有如下改进:
- 在JUnit3中需要继承TestCase类,但在JUnit4中已经不需要继承TestCase
- 可以使用类似@Test, @Before, @After等注解来管理自己的测试方法。在JUnit3中需要覆盖TestCase中的setUp和tearDown方法,其中setUp方法会在测试执行前被调用以完成初始化工作,而tearDown方法则在结束测试结果时被调用,用于释放测试使用中的资源,而在JUnit4中,只需要在方法前加上@Before,@After
- 测试方法名不再需要以test开头。在JUnit3中对某个方法进行测试时,测试方法的命令是固定的,例如对addBook这个方法进行测试,需要编写名字为tetAddBook的测试方法,而在JUnit4中没有方法命令的约束,在方法的前面加上@Test,这就代表这个方法是测试用例中的测试方法
- 新的断言assertThat
- @BeforeClass 和 @AfterClass 。在JUnit3,如果所有的test case仅调用一次setUp()和tearDown()需要使用TestSetup类
- 测试异常处理@Test(expected = DataFormatException.class)
- 设置超时@Test(timeout = 1000)
- 忽略测试@Ignore
- 支持对assert方法的static导入。
- 增加了一些Assert方法;
使用 Junit 4 风格的 Espresso APIs 来编写的测试用例如下:
- @Before: 标识在运行测试方法之前运行的代码。可以支持同一个Class中有多个@Before,但是这些方法的执行顺序是随机的。该注解替代了JUnit 3中的setUp()方法。
- @After: 标识在运行测试方法结束之后运行的代码。可以在其中做一些释放资源的操作。该注解替代了JUnit 3中的tearDown()方法。
- @Test: 标识一个测试方法。一个测试类中可以有多个测试方法,每个测试方法需要用一个@Test注解来标识。
- @Rule: 简单来说,是为各个测试方法提供一些支持。具体来说,比如我需要测试一个Activity,那么我可以在@Rule注解下面采用一个ActivityTestRule,该类提供了对相应Activity的功能测试的支持。该类可以在@Before和@Test标识的方法执行之前确保将Activity运行起来,并且在所有@Test和@After方法执行结束之后将Activity杀死。在整个测试期间,每个测试方法都可以直接对相应Activity进行修改和访问。
- @BeforeClass: 为测试类标识一个static方法,在测试之前只执行一次。
- @AfterClass: 为测试类标识一个static方法,在所有测试方法结束之后只执行一次。
- @Test(timeout=
) : 为测试方法设定超时时间。
区别
- Espresso 侧重于应用内测试 + 不支持 WebView
- UiAutomator 支持 WebView,应用内测试也ok,但是 api 的优雅性不如 Espresso
Espresso 自动化测试
Google 新退出的自动化测试框架,除了不支持 WebView,其它的测试功能都能很好的支持。
获取 Context
Espresso 中获取 Context 是通过
测试用例是工程强相关的,上面的 MainActivity 测试工程的主 UI,ActivityTestRule 这个类提供了相应的 Activity 测试支持。
常规 View 测试
例子如下:
-
withId: 使用控件 ID 匹配一个 [Matcher],具体的api见文档
withText: 使用 String 返回一个 Matcher12public static Matcher<View> withId(final int id);public static Matcher<View> withText(String text); onView: 使用一个 Matcher 查找一个 ViewInteraction
12public static ViewInteraction onView(final Matcher<View> viewMatcher);public ViewInteraction check(final ViewAssertion viewAssert); // 配合 matches 来做校验操作-
123456789public static ViewAction click() // 点击事件public static ViewAction typeText(String stringToBeTyped) // 输入事件public static ViewAction closeSoftKeyboard() // 关闭键盘public ViewInteraction perform(final ViewAction... viewActions) // 执行一系列的 viewActions 操作// typeText 输入text// closeSoftKeyboard() 关闭软键盘onView(withId(R.id.editText)).perform(typeText(mValidStringToBeTyped), closeSoftKeyboard()).check(matches(withText(mValidStringToBeTyped)));
测试工程 vs 主工程
在写测试用例的过程,除了能通过 onView 获取到主工程中的 View 控件,这是一个 ViewInteraction 类型,它暴露的 api 接口有限,无法通过它来获取控件的内容,这个时候就需要其它方法了;比如我们需要获取 主工程中 TextView 或者 EditText 的内容等。
通过 Matcher,结合 check、matches 来一起达到验证的目的。
使用方法如下:
ListView Adapter 测试
View 滑动
这里只需要弄清楚一个 List 的滑动接口就好。123456789101112private static DataInteraction onRow(String str){// 滑动到value=str指定的item中// LongListActivity.ROW_TEXT 这个属性是在创建 Adapter 时对应子 View 的一个 id,后面的值是对应的 id 赋值DataInteraction dataInteraction = onData(hasEntry(equalTo(LongListActivity.ROW_TEXT), is(str)));try {sleep(3000); // 这里添加参数只是为了方便演示} catch (InterruptedException e) {e.printStackTrace();}return dataInteraction;// return onData(hasEntry(equalTo(LongListActivity.ROW_TEXT), is(str)));}子 View 的点击
1234// 滑到 TEXT_ITEM_30 这一行 点击相应的 id 操作onRow(TEXT_ITEM_30).onChildView(withId(R.id.rowToggleButton)).perform(click());// 滑到 TEXT_ITEM_30 这一行 找到相应的 View,判断 ToggleButton 点击态onRow(TEXT_ITEM_30).onChildView(withId(R.id.rowToggleButton)).check(matches(isChecked()));
异步测试
实际测试过程中,对于比较耗时的操作进行操作,需要考虑异步问题,这里可以采用测试工程通过主动 sleep 来解决,但是这个操作本身不够友好,此时可以通过 IdlingResource 来处理。
这个接口类说明如下:
这里可以给一个简单的实现
通过 IdlingResource 来完成异步测试需要三步:
在主工程中创建一个 IdlingResource,并暴露一个获取实例的方法供测试工程调用,可以使用注解 @VisibleForTesting
12345678public IdlingResource getIdlingResource() {if (mIdlingResource == null) {mIdlingResource = new SimpleIdlingResource();}return mIdlingResource;}在测试工程中调用上方法,将 IdlingResource 注册到系统中
1234567891011121314// 注册public void registerIdlingResource() {mIdlingResource = mActivityRule.getActivity().getIdlingResource();// To prove that the test fails, omit this call:Espresso.registerIdlingResources(mIdlingResource);}// 反注册public void unregisterIdlingResource() {if (mIdlingResource != null) {Espresso.unregisterIdlingResources(mIdlingResource);}}在主工程中调用下面两个方法完成耗时操作的标记
12idlingResource.setIdleState(false); // 来阻塞测试线程idlingResource.setIdleState(true); // 来放开测试线程
假设我们使用 processMessage 方法来处理一个耗时操作,内部使用handler,下面是示例代码:
Intent相关测试
这里会作为一个专题重点来讲
- Base
- Advance
比如 startActivityForResult 这种类型的跳转测试
多窗口测试
Android 系统允许多个 View 控件位于系统的最顶层目录上,只是层级不一样,这样会造成有的 View 控件被遮挡导致不可交互,那测试工程通过什么手段能知道当前是哪些 View 控件处理可交互的状态,哪些是不可交到状态呢。
UiAutomator 自动化测试
这个提供了 Android 手机上应用间的自动化测试,它可以很方便的模拟人在使用手机过来中出现的各种操作,并提供了相关的验证方法;
UiAutomator 与 Espresso 框架有一个不同的地方在于获取 Context。
InstrumentationRegistry
这是一个暴露的注册实例,持有 instrumentation 运行的进程和参数,还提供了一种简便的方法调用 instrumentation, application context 和 instrumentation 参数。
它主要用来获取 Context 及 Instrumentation 实例。
注意:自动化测试代码 跟 Android 工程代码实际上没有什么区别,所以在有 Context 的情况下,能做的事情很多,比如关闭打开 Wifi,启动 Activity 应用等等。
|
|
UiDevice
这个是 uiautomator 提供的一个操作设备的类,它可以模拟各种设备相关的操作,比如按击Home键,菜单栏等,它包含了大量的 API,具体可以参考上面的文档。
|
|
By + BySelector + Until
这是很实用的一组工具类,三者结合可以用来完成查找或修改及验证(Until类)功能,上面附有对应的文档,可以查阅API,下面介绍一下简单的使用:
|
|
wait 判断
测试用例中用来的超时等待,比如我要判断当前某一个应用当前是否在最上层,并且可以设置超时时间,这个 wait的还会返回一个 Object 实体。
|
|
下面这个理解一下,wait 操作返回一个 Object,然后获取这个 Object 的内容
该操作相比 mDevice.findObject,只是多加了一个超时等待
|
|
findObject
该接口返回一个实体组件,然后再调用对应的方法,入参为包句,及 id
assertThat 验证
|
|
check 方法
这里罗列一些常用的check方法:
anyof 接口
|
|