来自:Test UI for a single app
测试单个应用程序中的用户交互有助于确保用户在与应用程序交互时不会遇到意外结果或体验不佳。如果需要验证应用程序的UI是否正常工作,您应该养成创建用户界面(UI)测试的习惯。
Android Test提供的Espresso测试框架提供了用于编写UI测试的API,用于模拟单个目标应用程序中的用户交互。Espresso测试框架可以运行在Android 2.3.3(API级别10)以及版本更高的设备上。使用Espresso的一个主要好处是它提供了测试操作与测试应用程序的UI的自动同步。Espresso检测主线程何时空闲,因此它可以在适当的时间运行测试命令,从而提高测试的可靠性。这种能力也使您免于在测试代码中添加任何时间处理方法,如Thread.sleep()。
Espresso测试框架是一个基于Instrumentation API并与AndroidJunitRunner一起工作的测试运行器。
准备Espresso
在使用Espresso构建UI测试之前,请确保配置测试源代码位置和项目依赖。详情见: Getting Started with Testing.
在Android app 模块的build.gradle 文件中,必须设置Espresso库的依赖引用:
dependencies {
// Other dependencies ...
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
关闭测试设备上的动画:在测试设备中打开系统动画可能会导致意外的结果或可能导致测试失败。关闭动画通过打开设置里的开发人员选项,关闭如下选项:
- 窗口动画缩放
- 过渡动画缩放
- 动画程序时长缩放
如果您想将项目设置为使用Espresso的其他特性(除了Espresso核心API提供),见 资源
创建Espresso测试类
要创建Espresso测试,请遵循如下编程模型:
- 通过调用onView()方法或AdapterView控件的onData()方法,查找Activity(例如,应用程序中的登录按钮)中要测试的UI组件。
- 通过调用ViewInteraction.perform() 或DataInteraction.perform() 方法并传递用户操作(例如,单击登录按钮),模拟一个在UI组件上执行的特定的用户交互。若要在同一UI组件上排序多个操作,请使用方法参数中的逗号分隔列表将它们链接起来。
- 根据需要重复上面的步骤,模拟用户在目标应用程序中经过多个Activity。
- 在执行上述用户交互之后,使用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
使用Espresso 和ActivityTestRule
以下部分描述如何以JUnit 4样式创建新的Espresso测试,并使用ActivityTestRule来减少需要编写的样板代码量。通过使用ActivityTestRule,测试框架在每个以@Test注解的测试方法以及任何以@Before注解的测试方法之前启动测试活动。该框架在测试完成后处理关闭活动,并运行所有使用@After注释的方法。
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可以与被测试的应用程序交互之前,您必须首先指定UI组件或视图。Espresso支持使用Hamcrest 匹配器来指定应用程序中的视图和适配器。若要查找视图,请调用onView()方法,并传入指定目标视图的视图匹配器。后面更详细描述这点。onView()方法返回一个ViewInteraction对象,允许您的测试与视图交互。但是,如果要在RecyclerView布局中查找视图,则调用onView()方法可能不起作用。在这种情况下,请按照在AdapterView中查找视图中的说明进行操作。
注:onView()方法不检查您指定的视图是否有效。相反,Espresso仅使用提供的匹配器搜索当前视图层次结构。如果未找到匹配项,则该方法将抛出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.
...
}
指定视图匹配器
您可以使用以下方法指定视图匹配器:
-
在ViewMatchers类中调用方法。 例如,要通过查找显示的文本字符串来查找视图,可以调用如下方法:
onView(withText("Sign-in"));
同样,您可以调用withId()并提供视图的资源ID(R.id),如以下示例所示:
onView(withId(R.id.button_signin));
Android资源ID不保证是唯一的。如果您的测试尝试多个视图匹配一个资源ID,Espresso将抛出AmbiguousViewMatcherException。
-
使用Hamcrest Matchers类。您可以使用allOf()方法组合多个匹配器,例如containsString()和instanceOf()。此方法允许您精细地过滤匹配结果,如以下示例所示:
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
您可以使用not关键字筛选与匹配器不对应的视图,如以下示例所示:
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
要在测试中使用这些方法,请导入org.hamcrest.Matchers包。 要了解有关Hamcrest匹配的更多信息,请参阅 Hamcrest site。
要提高Espresso测试的性能,请指定查找目标视图所需的最小匹配信息。例如,如果视图可由其描述性文本唯一标识,则无需指定它也可从TextView实例分配。
在AdapterView中找到一个视图
在AdapterView小部件中,视图在运行时使用子视图动态填充。如果要测试的目标视图位于AdapterView内(例如ListView,GridView或Spinner),则onView()方法可能不起作用,因为在当前视图层次结构中只能加载一部分视图。Espresso将目标视图元素加载到当前视图层次结构中。Espresso还负责滚动到目标元素,并将元素置于焦点。
注:onData()方法不检查您指定的项是否与视图对应。Espresso仅搜索当前视图层次结构。 如果未找到匹配项,则该方法将抛出NoMatchingViewException。
以下代码片段显示了如何将onData()方法与Hamcrest匹配一起使用来搜索包含给定字符串的列表中的特定行。 在此示例中,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() 点击视图
- ViewActions.typeText() 单击视图并输入指定的字符串。
- ViewActions.scrollTo() 滚动到视图。目标视图必须是ScrollView的子类,并且其android:visibility属性的值必须是VISIBLE。对于扩展AdapterView的视图(例如,ListView),onData()方法负责滚动。
- ViewActions.pressKey() 使用指定的keycode执行按键操作。
- ViewActions.clearText() 清除目标视图中的文本。
如果目标视图位于ScrollView内,请首先执行ViewActions.scrollTo()操作以在其他操作进行之前在屏幕中显示视图。如果已显示视图,则ViewActions.scrollTo()操作将不起作用。
使用Espresso Intents隔离测试Activity
Espresso Intents可以验证和存储应用程序发出的意图。使用Espresso Intents,您可以通过拦截传出意图,对结果进行存根并将其发送回被测组件来单独测试应用,活动或服务。要开始使用Espresso Intents进行测试,您需要将以下行添加到应用程序的build.gradle文件中:
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
}
要测试意图,您需要创建IntentsTestRule类的实例,这与ActivityTestRule类非常相似。IntentsTestRule类在每次测试之前初始化Espresso Intents,终止主机活动,并在每次测试后释放Espresso Intents。
@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 documentation on the Android Test site. 您还可以下载 IntentsBasicSample和 IntentsAdvancedSample代码示例。
使用Espresso Web测试WebViews
Espresso Web允许您测试活动中包含的WebView组件,它使用WebDriver API来检查和控制WebView的行为。要开始使用Espresso Web进行测试,您需要将以下行添加到应用程序的build.gradle文件中:
dependencies {
// Other dependencies ...
androidTestImplementation 'com.android.support.test.espresso:espresso-web:3.0.2'
}
使用Espresso Web创建测试时,需要在实例化ActivityTestRule对象以测试活动时在WebView上启用JavaScript。在测试中,您可以选择WebView中显示的HTML元素并模拟用户交互,例如在文本框中输入文本然后单击按钮。操作完成后,您可以验证网页上的结果是否与您期望的结果相匹配。
在以下代码片段中,该类在正在测试的活动中测试具有id值'webview'的WebView组件。 typeTextInInput_clickButton_SubmitsForm()测试选择网页上的<input>元素,输入一些文本,并检查出现在另一个元素中的文本。
@LargeTest
@RunWith(AndroidJUnit4.class)
public class WebViewActivityTest {
private static final String MACCHIATO = "Macchiato";
private static final String DOPPIO = "Doppio";
@Rule
public ActivityTestRule<WebViewActivity> mActivityRule =
new ActivityTestRule<WebViewActivity>(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 Test site. 您还可以下载 Espresso Web code sample代码段作为Espresso Web代码示例的一部分。
验证结果
调用ViewInteraction.check()或DataInteraction.check()方法来断言UI中的视图与某些预期状态匹配。您必须传入ViewAssertion对象作为参数。如果断言失败,Espresso会抛出AssertionFailedError。
ViewAssertions类提供了用于指定公共断言的辅助方法列表。 您可以使用的断言包括:
- doesNotExist 断言当前视图层次结构中没有与指定条件匹配的视图。
- matches 断言指定的视图存在于当前视图层次结构中,并且其状态与某些给定的Hamcrest匹配器匹配。
- selectedDescendentsMatch 断言存在父视图的指定子视图,并且它们的状态与某些给定的Hamcrest匹配器匹配。
以下代码段显示了如何检查UI中显示的文本是否与先前在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测试
您可以从Android Studio或命令行运行Espresso测试。 确保将AndroidJUnitRunner指定为项目中的默认检测运行器。要运行Espresso测试,请按照 Getting Started with Testing中所述的运行检测测试的步骤进行操作。
您还应该阅读Espresso API Reference。
有关完整的样品选择,请参阅Espresso Code Samples。也可见 Android Testing Codelab。