单个应用的UI测试(Espresso)

来自: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测试,请遵循如下编程模型:

  1. 通过调用onView()方法或AdapterView控件的onData()方法,查找Activity(例如,应用程序中的登录按钮)中要测试的UI组件。
  2. 通过调用ViewInteraction.perform() 或DataInteraction.perform() 方法并传递用户操作(例如,单击登录按钮),模拟一个在UI组件上执行的特定的用户交互。若要在同一UI组件上排序多个操作,请使用方法参数中的逗号分隔列表将它们链接起来。
  3. 根据需要重复上面的步骤,模拟用户在目标应用程序中经过多个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

使用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. 您还可以下载 IntentsBasicSampleIntentsAdvancedSample代码示例。

使用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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354