espresso
本篇详细介绍了Espresso的使用方式.
Espresso 测试代码位置和静态导入
Espresso 测试代码必须放在 app/src/androidTest 目录下.
为了简化 Espresso API 的使用, 强烈建议使用以下静态导入. 可以允许在没有类前缀的前提下访问这些静态方法.
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
ViewMatcher 的使用
查找一个 View, 需要使用 onView() 方法和视图匹配器(view matcher)来选择正确的 View. 如果你使用的是 AdapterView, 需要使用 onData() 方法而不是 onView()方法. onView() 返回的是一个 ViewInteraction 类型的对象, onData() 返回的是一个 DataInteraction 类型的对象.
表1 中介绍了可用的匹配器.
表 1 Espresso 匹配器
执行操作(perform action)
ViewInteraction 和 DataInteraction 可以通过 perform 方法和 ViewActions 类型的对象指定某种操作. ViewActions 类为最常见的操作了提供了相应的辅助方法. 像:
- ViewActions.click()
- ViewActions.typeText()
- ViewActions.pressKey()
- ViewActions.clearText()
- ViewActions.replaceText()
perform() 方法还会再次返回一个 ViewInteraction 的对象, 也就是说你可以执行更多的操作或者验证结果. 而且你也可以通过 perform() 方法的可变参数来指定多个操作.
验证测试结果
调用 ViewInteraction.check() 方法可以断言一个 View 的状态. 这个方法需要一个 ViewAssertion 类型的对象作为参数. ViewAssertion 类提供了用于创建这些对象的辅助方法.
- maches() - Hamcrest 匹配器
- doesNotExist() - 断言选中的 View 不存在
你也可以使用强大的 Hamcrest 匹配器. 下面给出了部分示例:
onView(withText(startsWith("ABC"))).perform(click());// 1.
onView(withText(endsWith("YYZZ"))).perform(click()); // 2.
onView(withId(R.id.viewId)).
check(matches(withContentDescription(containsString("YYZZ")))); // 3.
onView(withText(equalToIgnoringCase("xxYY"))).perform(click()); // 4.
onView(withText(equalToIgnoringWhiteSpace("XX YY ZZ"))).perform(click()); // 5.
onView(withId(R.id.viewId)).check(matches(withText(not(containsString("YYZZ"))))); // 6.
① 匹配以文本 “ABC” 开头的 View
② 匹配以文本 “YYZZ” 结尾的 View
③ 匹配指定 id View 的且检查文本内容包含 “YYZZ”
④ 匹配一个含有指定字符串的 View, 并且该字符串忽略大小写
⑤ 匹配一个含有指定字符串的 View, 并且该字符串忽略空白字符
⑥ 匹配指定 id View 的且检查文本内容不包含 “YYZZ”
访问 instrumentation API
通过 InstrumentationRegistry.getTargetContext() 可以访问到测试应用的上下文, 例如你想使用字符串 id 而不是 R.id, 下面的方法可以帮助你实现:
/**
* Created by zhanglulu on 2018/3/6.
*/
@RunWith(AndroidJUnit4.class)
public class EspressoTest2ActivityTest {
@Rule
public ActivityTestRule<EspressoTest2Activity> mRule =
new ActivityTestRule<EspressoTest2Activity>(
EspressoTest2Activity.class);
@Test
public void buttonShouldUpdateText(){
onView(withId(R.id.resultView)).perform(click());
onView(withId(getResourceId("Click"))).check(matches(withText("Done")));
}
/**
* 根据 id 字符串获取真实 id
* @param idStr
* @return
*/
private static int getResourceId(String idStr) {
Context targetContext = InstrumentationRegistry.getTargetContext();
String packageName = targetContext.getPackageName();
return targetContext.getResources().getIdentifier(idStr, "id", packageName);
}
}
配置Activity的启动 Intent
如果你给 ActivityTestRule 构造方法的第三个参数指定为 false, 则可以配置启动 Intent, 并手动启动测试 Activity.
/**
* Created by zhanglulu on 2018/3/6.
*/
@RunWith(AndroidJUnit4.class)
public class EspressoTest2ActivityTest {
@Rule
public ActivityTestRule<EspressoTest2Activity> mRule =
new ActivityTestRule<EspressoTest2Activity>(
EspressoTest2Activity.class, true, false);
@Test
public void demonstrateIntentPrep() {
Intent intent = new Intent();
intent.putExtra("input", "Test");
mRule.launchActivity(intent);
onView(withId(R.id.resultView)).check(matches(withText("Test")));
}
}
Adapter Views
AdapterView 是一种特殊的组件, 它可以动态的从 Adapter 中加载数据. 只有一部分数据在 View 层次结构中有真实的 View. onView() 不会为这些数据找到相应的 View. 而 onData() 可以与 Adapter 进行交互, 找到对应的 View, 例如 ListView. 下面给出部分示例:
@Test
public void testList() {
// 点击 ListView 中文本为 "测试 15" 的 item
onData(allOf(is(instanceOf(String.class)), is("测试 30")))
.perform(click());
onData(allOf(is(instanceOf(String.class)), is("测试 1")))
.perform(click());
onData(allOf(is(instanceOf(String.class)), is("测试 49")))
.perform(click());
}
Espresso 测试添加权限
可以通过 instrumentation API 给你的测试执行授予权限的操作.如下面示例:
@Before
public void grantPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand(
"pm grant " + getInstrumentation().getTargetContext().getPackageName()
+ " android.permission.CALL_PHONE");
}
}
Espresso UI Recorder
Android Studio 在测试菜单栏上提供了一个 Run ▸ Record Espresso Test 的功能, 它可以记录下你与应用程序的交互并从中创建 Espresso 测试.
点击运行,之后 Android Studio 将记录下你与应用的交互行为,并生成测试代码.
访问当前的 Activity
你还可以访问正在进行测试的 Activity 对象,并调用它的方法. 例如,你有下面的 TestConfigureActivity, 并存在一个方法 configureActivity().
public class TestConfigureActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_configure);
}
// 外部调用
public void configureActivity(String Uri) {
// do something with this
}
public void onClick(View view) {
startActivity(new Intent(this, SecondActivity.class));
}
}
如果你想在测试方法中得到这个 Activity 并且执行里面的方法, 你可以通过 ActivityTestRule 来获取, 示例如下:
@Test
public void useAppContext() throws Exception{
TestConfigureActivity activity = mRule.getActivity();
activity.configureActivity("https://testurl");
//do more
}
并且你也可以重写 ActivityTestRule 类来配置你的测试 Activity.例如:
@Rule
public ActivityTestRule<TestConfigureActivity> mRule =
new ActivityTestRule<TestConfigureActivity>(TestConfigureActivity.class){
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
//Activity 启动前做些事情
}
@Override
protected void afterActivityLaunched() {
super.afterActivityLaunched();
//Activity 启动后做些事情
}
};
你甚至可以拿到前台的 Activity 的实例.这里着重说明一下, 你所测试的 Activity 不一定都在前台显示, 例如,你从当前测试的 Activity (也就是你在 ActivityTestRule 传入的 Activity) 跳转到另外的一个 Activity, 此时在前台的 Activity 就是另外的一个 Activity. 这个时候就可以通过 ActivityLifecycleMonitorRegistry 来获取前台的 Activity, 示例如下:
@Test
public void navigate() {
onView(withText("Next")).perform(click());
Activity activity = getActivityInstance();
boolean b = (activity instanceof SecondActivity);
assertTrue(b);
// do more
}
public Activity getActivityInstance() {
final Activity[] activity = new Activity[1];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
Activity currentActivity = null;
Collection resumedActivities =
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()){
currentActivity = (Activity) resumedActivities.iterator ().next();
activity[0] = currentActivity;
}
});
return activity[0];
}
需要注意的是 ActivityLifecycleMonitorRegistry 并不是标准的 API, 所以可能在后期名称会发生变化.
运行 Espresso 测试输出测试报告
- 使用 Android Studio
右击你的测试类, 并点击运行就 OK 了.
- 使用 Gradle
通过 Gradle 执行 connectedCheck Task 可以执行测试目录下所有的测试用例.
执行结束后将会在 app/build/reports 目录下生成测试报告, 点击打开 index.html 即可查看.
检查 Taost
下面是一个点击 Item ,并检查 Toast 是否弹出的测试.
@Test
public void testToast() {
onData(allOf(instanceOf(String.class), is("测试 20"))).perform(click());
onView(withText(startsWith("点击")))
.inRoot(withDecorView(not(is(mRule.getActivity().getWindow().getDecorView()))))
.check(matches(isDisplayed()));
}
通过Espresso Intents模拟Intent
Espresso 也提供了模拟 Intent 的功能. 它可以检查一个 Activity 是否正常发出一个正确的 Intent.如果它接收了正确 Intent 则测试通过.
Espresso Intents 通过 com.android.support.test.espresso:espresso-intents 依赖提供.
使用 Espresso Intents 必须使用 IntentTestRule 而不是 ActivityTestRule.下面是一个简单的示例:
/**
* Created by zhanglulu on 2018/3/7.
*/
@RunWith(AndroidJUnit4.class)
public class TestIntent {
@Rule
public IntentsTestRule<EspressoTest1Activity> mIntentTestRule =
new IntentsTestRule<EspressoTest1Activity>(EspressoTest1Activity.class);
@Test
public void triggerIntentTest() {
onView(withId(R.id.switchActivity)).perform(click());
intended(allOf(
hasAction(Intent.ACTION_CALL),
hasData("123456789"),
toPackage(InstrumentationRegistry.getTargetContext().getPackageName()),
hasExtra("input", "Test")
));
}
}
更多详细内容请移步: https://developer.android.com/training/testing/espresso/intents.html#matching