- 单APP的UI测试
- 设置Espresso
- 创建一个Espresso测试类
- 同ActivityTestRule使用Espresso
- 获取UI组件
- 指定view matcher
- 查找AdapterView中的view Locating a view in an AdapterView
- 执行动作
- 使用 Espresso Intents隔离测试你的activities
- 使用 Espresso Web测试WebViews
- 验证结果
- 在设备或者模拟器上运行Espresso测试
单APP的UI测试
测试一个APP内的用户交互,有助于确保当用户与你的APP交互时,不会遇到未预料到的结果或者有一个糟糕的体验。如果你需要验证你的APP的UI功能正确,你应当养成创建用户界面(UI)测试的习惯。
由 Android Testing Support Library(安卓测试支持库) 提供的 Espresso 测试框架,提供了编写UI测试以模拟单个APP内的用户交互的API, Espresso测试能够运行在运行Android 2.3.3 (API level 10) 或者更高版本的设备上,使用Espresso的一个关键好处是,对于在测试APP的UI,它提供了测试动作的自动化同步。Espresso侦测何时主线程空闲,所以它可以在合适的时机运行测试指令。这让你从必须添加timing workarounds,比如Thread.sleep()中解脱出来。
Espresso测试框架是基于设备的API,与 AndroidJUnitRunner 协同工作。
设置Espresso
在用Espresso构建你的UI测试,确保配置你的源代码路径以及工程依赖,如 Android测试官方教程翻译(一)–Android 测试介绍 所述。
在你APP模块的build.gradle文件中,你必须设置Espresso库的依赖引用。
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}
关闭测试设备,测试设备系统动画保持开启可能引起异常结果或者导致测试失败。打开设备开发者选项,关闭以下所有选项以从设置关闭动画。
- Window animation scale
- Transition animation scale
- Animator duration scale
除了核心API提供的功能,如果你想设置你的工程使用Espresso特性,戳这儿。
创建一个Espresso测试类
依照如下编程模型创建一个JAVA类,以创建一个 Espresso 测试。
1. 通过调用 onView() 方法或者为 AdapterView控制设计的 onData()方法,在一个Activity中找到你想测试的UI组件(比如一个APP中的登陆按键)。
2. 通过调用 ViewInteraction.perform() 或者 DataInteraction.perform()方法,并且传入用户动作参数(比如点击登陆按键),模拟特定用户交互来执行在UI组件上。为了顺序排列同一UI组件上的多个动作,在你的方法参数中用逗号分隔符将动作串成表。
3. 如有需要重复以上动作,来模拟目标APP中跨多个Activity的用户操作流。
4. 在交互执行之后,使用 ViewAssertions 方法检测UI反映了期望的状态或者行为。
如下片段中,对这些步骤有更详尽描述。
如下代码片展示了你的测试类可能如何调用基本工作流。
onView(withId(R.id.my_view)) //withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
同ActivityTestRule使用Espresso
如下片段描述了如何依照JUnit 4风格创建一个新的 Espresso测试,并且使用 ActivityTestRule来减少你需要编写的样板代码的工作量。在每一个具有@Test注释的测试方法和任何有@Before注释的方法之前,测试框架启动测试Activity。在测试结束,并且所有具有 @After注释的方法运行之后,框架完全终止Activity。
package com.example.android.testing.espresso.BasicSample;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
...
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ChangeTextBehaviorTest {
private String mStringToBetyped;
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class);
@Before
public void initValidString() {
// Specify a valid string.
mStringToBetyped = "Espresso";
}
@Test
public void changeText_sameActivity() {
// Type text and then press the button.
onView(withId(R.id.editTextUserInput))
.perform(typeText(mStringToBetyped), closeSoftKeyboard());
onView(withId(R.id.changeTextBt)).perform(click());
// Check that the text was changed.
onView(withId(R.id.textToBeChanged))
.check(matches(withText(mStringToBetyped)));
}
}
获取UI组件
在Espresso能够与测试中的APP交互之前,你必须首先明确UI组件或者view,对于明确APP中的View或者Adapter,Espresso支持 Hamcrest 的使用。
为了找到view,调用 onView()方法并且传入明确目标View的view matcher。 onView()方法返回一个允许你的测试与view交互的 ViewInteraction 对象。然而对于 RecyclerView布局中的view,调用onView()可能并不奏效。这种情况下,参照下文Locating a view in an AdapterView 的说明。
注意: onView() 方法并不检查你指定的view是否有效。相反,Espresso使用matcher,仅仅搜寻当前view层级,如果未发现匹配的,这个方法抛出 NoMatchingViewException异常。
如下代码片展示了你如何编写获取一个 EditText域,输入文本串,关闭虚拟键盘,然后执行按键点击的测试。
public void testChangeText_sameActivity() {
// Type text and then press the button.
onView(withId(R.id.editTextUserInput))
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
onView(withId(R.id.changeTextButton)).perform(click());
// Check that the text was changed.
...
}
指定view matcher
通过如下方法,你可以明确一个view matcher
- 调用 ViewMatchers 类中的方法。比如,通过查找所展示文本找到一个view,你可以这样调用方法。
onView(withText("Sign-in"));
相似的,你可以调用 withId()方法,提供view的资源ID,如下示例所示。
onView(withId(R.id.button_signin));
Android的资源ID并不确保是唯一的。如果你的测试尝试匹配一个被多个view使用的ID,Espresso抛出 AmbiguousViewMatcherException异常。
- 使用Hamcrest 的Matchers类,你可以使用allOf() 方法组合多个 matchers,比如 containsString()和instanceOf()。这种方法让你能够更苛刻的过滤匹配结果,如下示例所示。
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
过滤并不符合matcher的view,你可以使用not关键词,如下示例所示。
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
在你的测试中使用这些方法,需要导入org.hamcrest.Matchers包。学习更多Hamcrest matching,戳这儿 。
为了提高Espresso测试的性能,明确需要查找到目标view的最小匹配信息。
查找AdapterView中的view (Locating a view in an AdapterView)
在一个 AdapterView 中,子view是在运行时动态加载的,如果你测试的目标view是在一个AdapterView中(比如 ListView, GridView, o或者Spinner), onView()方法可能并不奏效,因为在当前view层级中,可能仅仅view的一个子集被加载。
相反,调用 onData()方法,以包含一个 DataInteraction 对象,来获取目标view。Espresso处理加载目标view到当前view层级。Espresso也会滑动到目标view元素,并让其获取焦点。
注意:onData()方法并不检测你指定的条目和一个view相符合。Espresso 仅仅搜寻当前view层级,如果没有发现匹配的,该方法抛出 NoMatchingViewException异常。
如下代码片向你展示你如何和 Hamcrest matching 一起使用 onData()方法,来搜寻一个list中的包含给定文本的指定行。在这个示例中,LongListActivity包含了一个通过 SimpleAdapter暴露的字符串列表。
onData(allOf(is(instanceOf(Map.class)),
hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input")));
执行动作
调用 ViewInteraction.perform() 或者 DataInteraction.perform() 方法来模拟UI组件上的用户交互。你必须传入一个或者多个 ViewAction 对象作为参数。 Espresso根据给定顺序,按序列引发动作,在主线程执行动作。
ViewActions类为通常的动作提供了一系列帮助方法。替代创建和配置个人的ViewAction对象,你可以使用这些方法作为便捷操作。你可以如此明确动作。
- ViewActions.click():点击view。
- ViewActions.typeText():点击view并且输入指定的字符串。
- ViewActions.scrollTo():滑动到view。目标view必须是 ScrollView的子类,并且它的 android:visibility属性必须是visibility。对于继承自 AdapterView的view(比如listview), onData()方法为你处理滑动。
- ViewActions.pressKey():使用制定的keycode执行按键按压。
- ViewActions.clearText():清楚目标view的文本。
如果目标view是在一个scrollview的内部,在其他动作执行之前,首先执行 ViewActions.scrollTo() 动作将view显示在屏幕上。如果view已经显示, ViewActions.scrollTo() 动作没有效果。
使用 Espresso Intents隔离测试你的activities
使用Espresso的intents,你可以通过阻断向外intent、stub结果,发送回测试中的组件,隔离的测试一个APP、activity或者service。
开始使用Espresso Intents测试,你需要添加以下行到你的build.gradle中。
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
}
为了测试intent,你需要创建一个 IntentsTestRule 类的实例,它和 IntentsTestRule 类非常相似。在任何测试之前, IntentsTestRule初始化Espresso Intents ,终止host activity,并且在每一个测试之后释放 Espresso Intents 。
以下代码片展示的测试类,提供了一个明确intent的简单测试。
@Large
@RunWith(AndroidJUnit4.class)
public class SimpleIntentTest {
private static final String MESSAGE = "This is a test";
private static final String PACKAGE_NAME = "com.example.myfirstapp";
/* Instantiate an IntentsTestRule object. */
@Rule
public IntentsTestRule≶MainActivity> mIntentsRule =
new IntentsTestRule≶>(MainActivity.class);
@Test
public void verifyMessageSentToMessageActivity() {
// Types a message into a EditText element.
onView(withId(R.id.edit_message))
.perform(typeText(MESSAGE), closeSoftKeyboard());
// Clicks a button to send the message to another
// activity through an explicit intent.
onView(withId(R.id.send_message)).perform(click());
// Verifies that the DisplayMessageActivity received an intent
// with the correct package name and message.
intended(allOf(
hasComponent(hasShortClassName(".DisplayMessageActivity")),
toPackage(PACKAGE_NAME),
hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)));
}
}
获取更多关于 Espresso Intents的信息,查阅 Espresso Intents ,你也可以下载 IntentsBasicSample 和 IntentsAdvancedSample 代码示例。
使用 Espresso Web测试WebViews
Espresso Web使你能够测试包含在一个activity中的webview,它使用 WebDriver API 来检测和和控制webview的行为。
开始使用Espresso Web进行测试,你需要添加以下行到你的APP的build.gradle文件中
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'
}
当使用 Espresso Web创建一个测试,当你实例化 ActivityTestRule对象来测试activity时,你需要使能webview的javascript。在测试中,你可以选择显示在webview中的HTML元素并且模拟用户交互,比如输入文本到一个文本框中,然后点击一个按键。当动作执行完,你可以验证web页显示的结果和你的预期结果一致。
以下代码片,这个类测试了测试Activity的id值为“webview”的 WebView。verifyValidInputYieldsSuccesfulSubmission()测试选择了web页的 元素,输入一些文本,检查另外一个元素中显示的文本。
@LargeTest
@RunWith(AndroidJUnit4.class)
public class WebViewActivityTest {
private static final String MACCHIATO = "Macchiato";
private static final String DOPPIO = "Doppio";
@Rule
public ActivityTestRule mActivityRule =
new ActivityTestRule(WebViewActivity.class,
false /* Initial touch mode */, false /* launch activity */) {
@Override
protected void afterActivityLaunched() {
// Enable JavaScript.
onWebView().forceJavascriptEnabled();
}
}
@Test
public void typeTextInInput_clickButton_SubmitsForm() {
// Lazily launch the Activity with a custom start Intent per test
mActivityRule.launchActivity(withWebFormIntent());
// Selects the WebView in your layout.
// If you have multiple WebViews you can also use a
// matcher to select a given WebView, onWebView(withId(R.id.web_view)).
onWebView()
// Find the input element by ID
.withElement(findElement(Locator.ID, "text_input"))
// Clear previous input
.perform(clearElement())
// Enter text into the input element
.perform(DriverAtoms.webKeys(MACCHIATO))
// Find the submit button
.withElement(findElement(Locator.ID, "submitBtn"))
// Simulate a click via JavaScript
.perform(webClick())
// Find the response element by ID
.withElement(findElement(Locator.ID, "response"))
// Verify that the response page contains the entered text
.check(webMatches(getText(), containsString(MACCHIATO)));
}
}
获取更多关于Espresso Web的信息,请查阅Espresso Web documentation on the Android Testing Support Library site ,你也可以下载 Espresso Web code sample 的代码。
验证结果
调用 ViewInteraction.check() 或者 DataInteraction.check() 来断言UI中的view匹配预期的状态。你必须传入一个 ViewAssertion对象作为参数。如果断言失败,Espresso抛出 AssertionFailedError异常。
ViewAssertions类为通用的断言提供了一系列帮助方法。你可用的方法包括:
- doesNotExist:断言在当前view层级中没有view匹配指定的标准。
- matches:断言在当前view层级中指定view存在,并且它的状态匹配一些给定的Hamcrest matcher
- selectedDescendentsMatch:断言父view的一个指定子view存在,并且它的状态匹配一些给定的Hamcrest matcher。
以下代码片展示了你可能如何检查UI中显示的text和之前在EditText域中输入的文本值相同。
public void testChangeText_sameActivity() {
// Type text and then press the button.
...
// Check that the text was changed.
onView(withId(R.id.textToBeChanged))
.check(matches(withText(STRING_TO_BE_TYPED)));
}
在设备或者模拟器上运行Espresso测试
你可以从AndroidStudio或者命令行运行Espresso测试。确保在你的工程中指定 AndroidJUnitRunner作为默认设备运行器。
运行 Espresso测试,参照第一篇译文中描述的运行设备测试步骤。