测试App的UI
测试一个app的用户交互可以确保用户不会遇到未知的结果或者有不好的用户体验。荣誉感太确保你app的UI工作良好你需要形成一个良好的用户界面测试。
Espresso测试框架,提供了 Android Testing Support Library,该Library种提供了模拟用户与一个app交互的界面测试API.Espresso的测试可以运行在Android2.3.3(API leve 10)及以上的设备上。使用Espresso的关键好处在于它能够将你所测试的app的测试动作与界面自动同步。Espresso能够检测出主线程何时处于空闲状态,所以能够在合适的时间运行测试命令。这个能力也减轻了你必须添加其它时间的方法,例如在代码中添加Thread.sleep()。
Espresso 测试框架以API指南为基础并运行在 AndroidJUnitRunner
运行器。
Espresso的设置
在通过Espresso创建UI测试文件之前,请按照Getting Started with Testing(https://developer.android.com/training/testing/start/index.html#config-instrumented-tests)所描述的配置测试的源代码的位置和项目的依赖。
在module层的build.gradle文件中,必需添加Espresso library的依赖:
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}
注:
build.gradle中添加依赖
关闭测试设备上的所有动画---保持设备上系统的动画打开可能引起意想不到的结果或者导致测试失败。打开设置》开发者选项,然后关闭下面的选项
---窗口动画缩放(Window animation scale)
---过渡动画缩放(Transition animation scale)
---动画程序时长缩放(Animator duration scale)
如果要设置Espresso核心API提供的功能之外的功能,请查看 resource(https://google.github.io/android-testing-support-library/docs/espresso/index.html)
注:
打开设置>开发者选项
关闭开发者选项中的窗口动画缩放、过渡动画缩放、动画程序时长缩放
创建一个Espresso的测试类
java类根据下面的模型来创建Espresso的测试类:
1.在Activity中通过方法onView()查找所要测试的控件(例如,app中的登陆按钮),或者在AdapterView中用onData()方法。
2.通过向方法ViewInteraction.perform()或DataInteraction.perform()方法中传入用户的动作(例如,点击登陆按钮)来模拟用户与特定UI组件的交互。对一个UI组件进行连续的操作,在方法中的参数用逗号分隔。
3.如有需要重复上面的动作,来模拟用户在app上不同界面的操作。
4.当用户操作完成后,通过ViewAssertions方法来检验UI是否是预期的状态或者行为一致。
下面的部分将更详细地介绍上面的步骤。
下面的代码片段是你的测试类可能包括的基本的工作流:
onView(withId(R.id.my_view)) // withId(R.id.my_view) 是一个控件匹配器
.perform(click()) // click() 是一个动作
.check(matches(isDisplayed())); // matches(isDisplayed()) 是一个控件的断言
Espresso中使用ActivityTestRule
这部分描述了如何在JUnit 4 style中创建一个新的Espresso测试类,并用ActivityTestRule来减少所需写的样板代码。通过使用ActivityTestRule,测试框架测试时会在声明了注解@Test和@Before的方法之前加载activity。框架控键将会在测试结束和所有声明了@After的方法运行完毕后关闭acitivity。
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。Espresso支持在app中使用 Hamcrest matchers(http://hamcrest.org/)来声明views和adapters。如果要查找控件,在方法onView()中传入目标view的view匹配器。更多详情请查看Specifying a View Matcher(https://developer.android.com/training/testing/ui-testing/espresso-testing.html#specifying-view-matcher)。方法onView返回一个ViewInteraction对象,该对象可以在测试时与view交互。然后,RecyclerView布局中的控件调用该方法可能会无效。这种情况下,请查看指南 Locating a view in an AdapterView (https://developer.android.com/training/testing/ui-testing/espresso-testing.html#locating-adpeterview-view)。
注意:方法onView()不会检查你所声明的view是否合法。Espresso搜索器只是根据匹配器在当前的view层级中进行搜索。如果没有查找到匹配的view,则抛出一个NoMatchingViewException.
如下所示是方法变量EditText时可能需要写的内容,输入文本,关闭虚拟键盘,然后点击按钮
public void testChangeText_sameActivity() {
// 输入文字然后关闭虚拟键盘.
onView(withId(R.id.editTextUserInput))
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
onView(withId(R.id.changeTextButton)).perform(click());
// 检查文字发生了改变
...
}
声明一个View匹配器
可以通过下面的方法来声明view匹配器:
---调用类ViewMatchers中的方法。例如,查找显示文字的一个view,你可以调用这样的方法:(根据控件显示的内容来查找)
onView(withText("Sign-in"));
类似的,你也可以将view的id作为方法withId()的参数来进行查找,如下所示:(根据控件的id来查找)
onView(withId(R.id.button_signin));
Android不能保证的资源ID唯一性。如果你的测试文件尝试通过匹配被多个view所引用的id,Espress会抛出一个AmbiguosViewMatcherException.
---使用Hamcrest中的类Matchers
.你可以通过allOf()方法来连接多个匹配,例如constainsStrings()和instanceOf().这种方式运行你缩窄匹配的结果,如下所示:
//即查找id是button_signin同时显示Sign-in的view
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
你也可以通过关键字not来过滤不匹配的view,如下所示:
//即id是button_signin,并且显示的不是Sign-out的view
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
如果要在你的测试中使用这些方法,需要导包
org.hamcrest.Matchers
。更多关于Hamcrest匹配器的内容,请查看
Hamcrest site(http://hamcrest.org/)
为了提高Espresso测试的性能,使用尽可能少的匹配信息来查找目标view。例如,如果一个view的描述信息是唯一的,你就不需要同时通过TextView的实例对象来查找。
在AdapterView中,在运行时将会被子view所污染。如果要查找AdapterView(例如ListView,GridView或者Spinner)中的view,由于方法onView()只查找在当前view层级中的子view所以调用该方法无效。
然而,可以调用方法onData()生成一个DataInteraction对象来访问目标view。Espresso控键会将目标view加载到当前的view层级中。Espresso也会处理滑动的目标元素,并将元素聚焦。
注意:方法onData()并不会检查该条目是否与view相符。Espresso搜索器值搜索当前的view层级。如果找不到匹配的view,则抛出异常NoMatchingViewException.
下面的代码片段如何通过方法onData()和Hamcrest匹配器来搜索集合中的特定内容的行。在这个例子中,类LongListActivity中的SimpleAdapter暴露了一个string类型的集合。
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必须设置为VISIBLE.继承AdapterView的view,方法onData()将会处理滚动事件。
---ViewActions.pressKey():按压指定的键值键
---ViewActions.clearText():清空目标view中的文字内容
如果目标view在ScrollView内,调用ViewActions.scrollTo()后,在其它操作作用于该view之前首先在屏幕中显示该view。如果该view已经显示,则ViewActions.scrollTo()将无效。
通过独立的Espresso Intents来测试你的activities
Espresso Intents运行从一个app中发送有效
在使用Espresso Intents开始测试之前,你需要将下面这行添加到app的build.gradle文件中。
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
}
为了测试一个intent,你需要创建与类ActivityTestRule类似的类IntentsTestRule的实例。类IntentsTest在每次测试之前初始化Espreso的intents,终止宿主activity,在每次测试之后释放Espresso Intents.
下面的代码片段是显性intent进行测试的例子。测试在 Building Your First App(https://developer.android.com/training/basics/firstapp/index.html)中的activity
@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";
/* 实例化一个IntentsTestRule对象. */
@Rule
public IntentsTestRule≶MainActivity> mIntentsRule =
new IntentsTestRule≶>(MainActivity.class);
@Test
public void verifyMessageSentToMessageActivity() {
// 在EditText中输入文字.
onView(withId(R.id.edit_message))
.perform(typeText(MESSAGE), closeSoftKeyboard());
// 通过显性的intent向另一个activity发送消息
onView(withId(R.id.send_message)).perform(click());
//检测目标activity收到了intent中包含的相应包名和消息
intended(allOf(
hasComponent(hasShortClassName(".DisplayMessageActivity")),
toPackage(PACKAGE_NAME),
hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)));
}
}
更多关于Espresso Intents的内容,请查看
Espresso Intents documentation on the Android Testing Support Library site(https://google.github.io/android-testing-support-library/docs/espresso/intents/index.html)。你也可以下载
IntentsBasicSample(https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IntentsBasicSample)和
IntentsAdvancedSample(https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IntentsAdvancedSample)代码示例。
通过Espresso的网站测试WebViews
Espresso网站允许你测试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来创建测试时,在初始化用于测试Activity的对象 ActivityTestRule时需要允许在 WebView
使用JavaScript。在测试中,你可以选择在WebView中显示的HTML元素和模拟用户交互,例如在文本框中输入文本或者点击按钮。当操作执行完后,你就可以根据网页上显示的内容确定结果是否符合预期。
在下面的代码片段中,类测试了一个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 /* 初始化触摸模式 */, false /* 加载Activity */) {
@Override
protected void afterActivityLaunched() {
// 允许JavaScript.
onWebView().forceJavascriptEnabled();
}
}
@Test
public void typeTextInInput_clickButton_SubmitsForm() {
//每次测试通过intent来启动一个Activity实现Activity的懒加载
mActivityRule.launchActivity(withWebFormIntent());
//选择布局中的WebView
//如果有多个WebView你也可以通过匹配器 onWebView(withId(R.id.web_view))来选择一个给定的WebView
onWebView()
// 通过ID查找元素
.withElement(findElement(Locator.ID, "text_input"))
// 清空之前的输入
.perform(clearElement())
// 在输入元素中输入内容
.perform(DriverAtoms.webKeys(MACCHIATO))
// 查找提交按钮
.withElement(findElement(Locator.ID, "submitBtn"))
// 通过JavaScript模拟点击
.perform(webClick())
// 通过 ID查找响应元素
.withElement(findElement(Locator.ID, "response"))
// Verify that the response page contains the entered text
//确认响应界面包含输入的内容
.check(webMatches(getText(), containsString(MACCHIATO)));
}
}
更多关于Espresso网页的内容,请查看
Espresso Web documentation on the Android Testing Support Library site。你也可以下载
Espresso Web code sample中部分的代码片段。
确认结果
调用方法ViewInteraction.check()
或者 DataInteraction.check()
来确定界面中的控件是否与预期的状态相符。并且将一个ViewAssertion
对象作为参数传入。如果断言失败,Espresso将会抛出一个AssertionFailedError.
类ViewAssertions
提供了操作常见断言的一系列帮助方法。可以使用的断言如下:
---doesNotExist
:断言当前的view层级中没有满足标准的view
---matches
:断言特定的view存在于当前的view层级中并且与给定的Hamcrest匹配器匹配
---selectedDescendentsMatch
:断言一个view存在父view,并且子view和父view与Hamcrest匹配器匹配
下面的代码片段演示了如何检测界面中U显示I的值与先前输入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(https://developer.android.com/training/testing/start/index.html#run-instrumented-tests)。
注:原文地址:https://developer.android.com/training/testing/ui-testing/espresso-testing.html#setup