Android自动化测试入门(三)Espresso
Espresso是谷歌力推的一个UI自动化测试框架,新建一个Andrdoid工程的时候默认就引入了Espresso的核心依赖,所以作为Android开发者,非常有必要学习这个框架。
之前使用UI Automator的时候,我们经常在不同的指令之间添加一个时间延时保证手机端执行完成,在Espresso直接使用onView(),onView()会等待界面执行完在执行下一步。
Espresso和UI Automator一样,也是在项目的app/src/androidTest文件夹下编写测试代码
下面先来个开胃菜
测试TextView是否已经显示
新建一个测试类HelloWorldTest1
2
3
4
5
6
7
8
9
10
11
12
13@RunWith(AndroidJUnit4.class)
public class HelloWorldTest {
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void listGoesOverTheFold() {
onView(withText("Hello World!")).check(matches(isDisplayed()));
}
}
HelloWorldTest非常简单,就是判断”Hello World!”是不是已经显示在屏幕上
- 通过
@Rule
注解来规定测试的Activity是MainActivity,并且测试完后关闭它 - 通过withText方法找到屏幕上的对应的控件
- onView方法可以等待寻找完成之后再工作。寻找完成之后,调用check方法来判断该控件是不是已经显示在屏幕上
- 点击类旁边的 run Test按钮 测试完成
Espresso 的API组成
onView()
和onData()
是Espresso和视图交互的入口都是用来定位一个View,onView()
是定位普通Vew,onData()
可以定位ListVew、GridView 中的一个条目- ViewMatchers: 实现了
Matcher<? super View>
接口,是onView()
的参数,将它传到onView()
中帮助定位一个View。 - ViewActions:对一个View的动作,比如点击
click()
,可以传递给ViewInteraction.perform()
方法 - ViewAssertions:用来断言测试结果是否正确,可以传递给
ViewInteraction.check()
方法。
测试点击界面上的一个按钮
创建一个clickTest()测试方法,点击界面上的某个按钮1
2
3
4
5
6@Test
public void clickTest(){
onView(withId(R.id.btn_click))
.perform(click())
.check(matches(isDisplayed()));
}
一般情况下使用withId方法通过View的id来找到一个View,不过有时候可能不同界面中使用了相同的id,这时候可以多个条件一起使用来定位一个View,比如下面1
2
3
4
5//id为R.id.my_view text为"Hello!"的控件
onView(allOf(withId(R.id.my_view), withText("Hello!")));
//既有text "7" 又有text "item: 0"的控件
onView(allOf(withText("7"), hasSibling(withText("item: 0"))))
.perform(click());
点击按钮,将TextView的文字由Hello World!改为Hello Espresso!,并检查结果。
app中的代码1
2
3
4
5
6
7final TextView textView = findViewById(R.id.tv_text);
findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText("Hello Espresso");
}
});
测试类中添加新的测试方法1
2
3
4
5
6
7@Test
public void matchText(){
//点击按钮,将Hello World!改为Hello Espresso!
onView(withId(R.id.btn_click)).perform(click());
//检查TextView上的文字是不是Hello Espresso!
onView(withId(R.id.tv_text)).check(matches(withText("Hello Espresso!")));
}
测试RecyclerView
测试RecyclerView,跳到RecyclerView的某一位置处执行点击
首先需要引入一个依赖espresso-contrib这里面包含包含 DatePicker、RecyclerView 和 Drawer 操作、无障碍功能检查以及 CountingIdlingResource
1 | androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0-alpha04' |
首先在应用的app中新建一个Activty来显示一个列表,接着前面的例子,在MainActivity中定义一个按钮点击进入列表页。1
2
3
4
5
6findViewById(R.id.btn_to_recyclerview).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(getApplicationContext(),RecyclerViewActivity.class));
}
});
测试根据position定位到某一条
新建一个RecyclerView的测试类RecyclerViewTest,测试根据position定位到某一条1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@RunWith(AndroidJUnit4.class)
public class RecyclerViewTest {
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void testRecyclerView(){
//点击进入列表页
onView(ViewMatchers.withId(R.id.btn_to_recyclerview)).perform(click());
//定位到第30条并点击
onView(ViewMatchers.withId(R.id.recyclerview))
.perform(RecyclerViewActions.actionOnItemAtPosition(30, click()));
//判断第30条的文字"item++++++++++++++30"是否显示了
onView(ViewMatchers.withText("item++++++++++++++30")).check(matches(isDisplayed()));
}
}
自定义匹配器来查找一个Item
还可以通过自定义匹配器来匹配一个Item是否存在,匹配器如下1
2
3
4
5
6
7
8
9
10
11
12
13
14public static Matcher<MyAdapter.ViewHolder> withAdaptedData(){
return new TypeSafeMatcher<MyAdapter.ViewHolder>(){
@Override
public void describeTo(Description description) {
description.appendText("测试item");
}
@Override
protected boolean matchesSafely(MyAdapter.ViewHolder item) {
return item.mTextView.getText().equals("item++++++++++++++30");
}
};
}
通过自定义匹配器滑动到RecyclerView的相应的位置,并判断该条item是否显示1
2
3
4
5
6
7
8
9@Test
public void matchItemData(){
//点击进入列表页
onView(ViewMatchers.withId(R.id.btn_to_recyclerview)).perform(click());
//根据匹配器滑动到相应的位置
onView(withId(R.id.recyclerview)).perform(RecyclerViewActions.scrollToHolder(withAdaptedData()));
//判断期望的文本有没有显示
onView(withText("item++++++++++++++30")).check(matches(isDisplayed()));
}
Espresso-Intents
Espresso-Intents是Espresso的一个扩展,支持对被测应用发出的intent 进行验证和打桩,适用于 Android Intent,可以判断Intent是否跳转成功,传递的参数是否正确。
使用它需要添加新的依赖1
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
编写Espresso-Intents之前,首先需要设置IntentsTestRule,ntentsTestRule 会在带有 @Test 注解的每个测试运行前初始化 Espresso-Intents,并在每个测试运行后释放 Espresso-Intents。1
2
3@Rule
public IntentsTestRule<MainActivity> intentsTestRule =
new IntentsTestRule<>(MainActivity.class);
Espresso中可以使用 intended()
和 intending()
这两个方法来验证一个Intent。
测试去一个界面选号码然后打电话的动作
app中的步骤是这样的,点击“获取号码”按钮,通过startActivityForResult
去联系人页面选择一个号码返回保存到成员变量中,然后点击“打电话”按钮通过Intent打开系统打电话的界面。下面是测试代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//测试去联系人页面获取一个电话号码
@Test
public void testPickIntent(){
//创建一个Intent用来封装返回的数据
Intent intent = new Intent();
intent.putExtra(ContactsActivity.KEY_PHONE_NUMBER,"123456789");
//验证如果有相应的startActivityOnResult发生,就返回前面自己创建的结果。
intending(IntentMatchers.hasComponent(ComponentNameMatchers.hasShortClassName(".ContactsActivity")))
.respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK,intent));
//点击去联系人页面的按钮
onView(withId(R.id.btn_to_contacts)).perform(click());
//点击打电话的按钮
onView(withId(R.id.btn_call)).perform(click());
//验证打电话的Intent是否发送
intended(allOf(
hasAction(Intent.ACTION_CALL),
hasData("tel:123456789")
));
}
- 上面的测试中,intending() 方法用来测试startActivityOnResult,当startActivityOnResult动作发生的时候,就返回自己创建的一个Intent
- intended() 方法用来测试startActivity打开打电话页面的时候Intent的参数是否正确。
测试打开系统相机获取图片的动作
动作:一个Button,一个ImageView,点击按钮拍照,拍照确定后将图片显示到ImageView上面
1 | @Test |
- 自定义一个BoundedMatcher,用来判断一个ImageView上面是否有图片
- 从资源文件中找一张图片,自定义一个Bitmap返回给带有
ACTION_IMAGE_CAPTURE
这个action的Intent - 点击拍照按钮之前和之后都检查一下ImageView上面是否有图片。拍照之前没有,拍照之后有则测试通过。
Espresso Web
Espresso Web用来测试Android中的WebView,混合应用,可以测试js和原生的交互
使用前首先引入依赖包1
androidTestImplementation 'androidx.test.espresso:espresso-web:3.2.0'
下面的例子中,使用官网Demo中的本地Html页面测试,里面有javaScript相关的操作。完整demo看文末链接
简单使用:1
2
3
4
5
6
7@Test
public void testWebViewHasDisplay(){
//点击进入WebViewActivity
onView(withId(R.id.btn_go_to_web)).perform(click());
//判断页面的标题是否显示了
onWebView().check(webMatches(Atoms.getTitle(),containsString("Hello Espresso Web")));
}
测试JavaScript,为了支持 JavaScript 求值,被测 WebView 必须启用 JavaScript。通过forceJavascriptEnabled()
方法来开启1
2
3
4
5
6
7
8@Rule
public ActivityTestRule<WebViewActivity> activityWebRule =
new ActivityTestRule<WebViewActivity>(WebViewActivity.class,false,false){
@Override
protected void afterActivityLaunched() {
onWebView().forceJavascriptEnabled();
}
};
测试:在输入框输入文字,点击某个按钮,将输入框中的文字,赋值给另一个标签。检测该标签上的文字是不是输入的文字。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20@Test
public void testJsInteraction(){
//点击进入WebViewActivity
onView(withId(R.id.btn_go_to_web)).perform(click());
onWebView()
//html界面上找到id为text_input的元素
.withElement(findElement(Locator.ID, "text_input"))
//清除之前的文字
.perform(DriverAtoms.clearElement())
//输入文字Lily
.perform(DriverAtoms.webKeys("Lily"))
//通过id找到切换文字的按钮
.withElement(findElement(Locator.ID, "changeTextBtn"))
//点击按钮
.perform(webClick())
//通过id找到显示文字的标签
.withElement(findElement(Locator.ID, "message"))
//判断标签上的文字是不是Lily
.check(webMatches(getText(), containsString("Lily")));
}
测试:在输入框中输入文字,点击提交按钮提交表单,检测表单里的值跟输入的文字是否一样1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20@Test
public void testJsFormSubmit(){
//点击进入WebViewActivity
onView(withId(R.id.btn_go_to_web)).perform(click());
onWebView()
// html界面上找到id为text_input的元素
.withElement(findElement(Locator.ID, "text_input"))
// 清除之前的文字
.perform(clearElement())
// 输入文字Lily
.perform(DriverAtoms.webKeys("Lily"))
// 通过id找到提交按钮
.withElement(findElement(Locator.ID, "submitBtn"))
// 点击按钮执行提交动作
.perform(webClick())
// 通过id找到form表单的id
.withElement(findElement(Locator.ID, "response"))
// 验证提交的内容是不是Lily
.check(webMatches(getText(), containsString("Lily")));
}
Espresso常用的功能就练习完了,还有一些不常用的功能可以点击去官网查看