6、单个应用的UI测试

来源:互联网 发布:深度睡眠知乎 编辑:程序博客网 时间:2024/06/05 16:28

单个应用的UI测试

测试单个应用中的用户交互可以帮助我们确保用户在使用使用应用 时不会遇到一些意想不到的结果或者遇到糟糕的用户体验。如果你需要验证你的应用的 UI 功能正确,你应该养成创建用户界面(UI)测试的习惯。

Android 测试支持库 提供的 Espresso 测试框架,提供了用于编写UI测试以模仿目标App中的用户交互的API。Espresso 测试可以运行在Android 2.3.3(API 10)以上的设备上。使用 Espresso 的一个关键的好处就是它提供了测试操作与正在测试的App的UI的自动同步。Espresso 可以观察到什么时候主线程是空闲的,所以他可以再合适的时间运行你的测试命令,提升测试的可靠性。这种能力可以减少你在测试测试代码中添加任何时间变通方法(例如 Thread.sleep())的麻烦。

Espresso 测试框架是基于 instrumentation 的API,并且在 AndroidJUnitRunner 测试运行器上运行。

设置 Espresso

在使用 Espresso 构建你的UI测试之前,请确保配置了你的测试代码的路径和项目的依赖,就像 测试基础 里面描述的一样。

在你的Android app module 中的 build.gradle 文件,你必须设置一个 Espresso 库的依赖关系引用:

dependencies {    // Other dependencies ...    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'}

关掉你的测试设备上的动画效果 - 测试设备上的系统动画打开可能会导致意外的结果或者可能使你的测试失败。打开开发者选项从设置中关调动化并且也关掉下面的几个选项:

  • 窗口动画缩放

  • 过渡动画缩放

  • 动画程序时长缩放

如果你想设置您的工程去使用 Espresso 的特性而不是核心API提供的功能,请看 resource

创建一个 Espresso 测试类

要想创建一个 Espresso 测试,请创建一个遵循下面编程模型的 Java 类:

  1. 通过调用 onView 方法或者用于 AdapterView 控制的 onData() 方法,来找到在 Activity 中你想测试的 UI 组件(例如,应用中的一个登录按钮)。

  2. 通过调用 ViewInteraction.perform() 或者 DataInteraction.perform() 方法传入用户操作来模拟一个执行在 UI 组件上的用户交互(例如,注册按钮的点击事件).要在同一个组件上对多个动作进行排序,使用你的方法参数中的逗号分割来链接他们。

  3. 根据需要重复上述步骤,以模拟目标应用多个界面的用户操作流。

  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

使用 Espresso 和 ActvityTestRule

下面的部分解释了应该如何使用 JUnit4 风格来创建一个新的 Espresso 测试,并使用 ActivityTestRule 来减少你需要编写的样板代码的数量。通过使用 ActivityTestRule,测试框架在每个使用 @Test 注解的方法和 任何使用 @Before 注解方法之前启动被测的 Activity. 在测试完成后,测试框架会结束测试的 Activity 并且所有 使用 @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)@LargeTestpublic 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组件或 view. Espresso 支持 Hamcrest marchers 的使用用来确认你的应用中的 view 或者 adapter.

为了找到view,调用 onView() 方法并传递进去一个 view matcher, 他知道该你了你的目标view. Specifying a View Matcher 描述了更多的细节. onView() 方法返回了一个 ViewInteraction 对象,允许你的测试与 view 交互。然而,如果你想在 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(R.id),像下面的例子展示的那样:

onView(withId(R.id.button_signin));

Android 资源 ID 不保证是唯一的。因此如果你的测试视图匹配多个 view 共同使用的资源 ID ,Espresso 将会抛出AmbiguousViewMatcherException异常.

  • 使用 Hamcrest Matchers 类,你可以使用 allOf() 来合并多个 matcher, 像 containsString()instanceOf() .这种方法可以使你更严格的过滤匹配结果,如以下示例所示:
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));

你可以使用 not 关键字来过滤不符合匹配的 view,如以下示例所示:

onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));

要在你的测试中使用这些方法,请导入 org.hamcrest.Matchers 包.要了解更多关于 Hamcrest matching,请看 Hamcrest site.

要提高你的 Espresso 测试的性能,请指定查找目标 view 所需要的最小的匹配信息。例如,过一个 view 可以通过他的描述的文本作为唯一标识,那你就不需要指定他,他也可以从 TextView 实例分配。

定位 AdapterView 中的 view

在一个 AdapterView 组件中,视图在运行时动态填充子视图。如果你想测试的目标 view 在 AdapterView (例如 ListView,GridView ,或者 Spinner)里面 onView() 方法可能不会起作用因为可能只会有一部分 view 在当前视图结构加载。

取而代之的是,调用 onData() 方法获得一个 DataInteraction 对象以访问目标视图元素。 Espresso 处理将目标视图加载到当前的视图结构中。 Espresso 也负责滚动目标元素,并将焦点放到元素上。

注意:onData() 方法不会检查你所制定的项目是否与视图对应。 Espresso 只会查找当前的视图结构。如果没有找到相匹配的,那么这个方法会抛出 NoMatchingViewException 异常。

下面的代码片段展示了你应该如何使用 onData 方法结合 Hamcrest maatching 去查找一个给定字符串列表中的确定项目。在这个例子中,LongListActivity 类包含通过 SimpleAdapter 暴露的字符串集合。

onData(allOf(is(instanceOf(Map.class)),        hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input")));

执行动作

调用 ViewInteraction.perform 或者 DataInteraction.pergorm() 方法来模拟UI组件上的用户交互。你必须传进去一个或多个 ViewAction 对象作为参数。 Espresso 按照给定的顺序依次触发每个操作,并且在主线程执行他们。

ViewActions 类提供了一系列用于指定常用动作的帮助方法。你可以使用这些方法作为辩解的快捷方式,而不用创建并配置一个独立的 ViewAction 对象。你可以指定如下操作:

  • ViewActions.cllick():点击视图

  • ViewActions.typeText(): 点击视图并且输入一个特定的字符串

  • ViewActions.scrollTo(): 滑动视图。目标视图必须是 ScrollView 的子类,并且他的 android:visibility 属性必须是 VISIBLE 。对于那些继承自 Adapter 的视图(例如 ListView ),onData() 方法将负责滚动。

  • ViewActions.pressKey(): 使用特定的键码执行按键操作。

  • ViewActions.clearText(): 清楚目标视图上的文本。

如果目标视图在 ScrollView 里面,先执行 ViewActions.scrollTo() 操作来显示目标是图然后再执行其他操作。如果视图已经显示了,那么 ViewActions.scrollTo() 操作没有影响。

使用 Espresso Intents 隔离测试你的 acticity

Espresso Intents 可以验证一个应用程序发出去的 intent 的 stub.通过使用 Espresso Intents,你可以通过拦截的发出去的 intents,对结果进行存根,并将其发送回被测组件来隔离测试应用组件,actiity 或者 service 。

要想开始使用 Espresso Intents 进行测试,请将下面这行添加到到你的 app 级别下的 build.gradle 文件。

dependencies {  // Other dependencies ...  androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'}

要想测试一个 intent,你需要创建一个 IntentTestRule 类的实例。IntentTestRule 类在每次测试之前初始化 Espresso Intents ,终止主 activity,并且在每次测试完成之后释放 Espresso Intents

下面代码片段所展示的测试类为明确的 intent 提供了一个简单的测试。他测试了你在 构建你的第一个应用程序 教程里面创建的 activities 和 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,请看 Android 测试支持库中的 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 元素并且模仿用户交互,例如向 text box 中输入文本并且点击按钮。在这些动作执行完毕后,你就可以验证网页上显示的结果是否如你所期望的结果相匹配。

在下面的代码片段中,该类测试了被测试activity中id为”webview”的 WebView 组件。
verifyValidInputYieldsSuccesfulSubmission() 测试选择了一个网页上的 < input> 元素,输入了一些文本,并且检查另一个元素上显示的文本。

@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,请看 Android 测试支持库中的 Espresso Web 文档。 你也可以下载 Espresso Web code sample 代码示例。

验证结果

调用 ViewInteration.check() 或者 DataInteration.check() 方法来断言用户界面上的视图是否匹配一些预期的状态。你必须传递一个 ViewAssertion 对象作为参数。如果断言失败了,那么 Espresso 将会抛出 AssertionFailedError 异常

ViewAssertions 类提供了一些列用于确定普通断言的帮助方法,你可以使用的这些铵盐包括:

  • doesNotExist: 断言当前视图结构中没有与指定条件所匹配的视图。

  • matches: 断言指定的视图存在于当前的视图结构中并且它的状态与给定的 Hamcrest matcher 相匹配。

  • selectedDescendentsMatch: 断言给定的子视图存在父视图,并且他们的状态与给定的 Hamcrest matcher 相匹配。

下面的代码片段展示了你应该如何检查 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 Tests

你可以在 Android Studio 上或者从命令行上运行测试。确定指定了 AndroidJUnitRunner 作为你项目的默认 instrumentation 测试。

原创粉丝点击