前言:
1.参考:
Android 单元测试实践
2.本文指androidTest,即运行在android真机/虚拟机 的测试,而非可直接运行在jvm的单纯的java test
导入依赖(包)
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
这几条一般自带就有。这里再增加一条:
androidTestImplementation group: 'com.android.support.test', name: 'rules', version: '1.0.2'
注意依赖方式是androidTestImplementation
,具体差异右转百度/官方文档。另外版本可自行上maven中央仓库查询后更改至最新。
相关类的意义
Espresso:与视图交互的入口(通过onView和onData)。
ViewMatchers: 相当于代表着一个View(如TextView等)。
ViewActions:代表着触发的动作(如click-点击、typeText-输入文本等)。
ViewAssertions:断言(用于判断等价之类的)
ViewInteraction:视图交互对象
注意:
1.主要是使用它们的静态方法,如onView、onData、withId等,详细请查看官方文档。另外Android Studio对静态方法不太敏感,可能不会提示你导入相关类静态方法,此时请手动增加相关静态方法导入,如
import static android.support.test.espresso.Espresso.*;
import static android.support.test.espresso.action.ViewActions.*;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static android.support.test.espresso.assertion.ViewAssertions.*;
2.关于android.support 包和androidx包。请注意区分它们的差异:difference-between-androidx-and-com-android-support。我这里用的还是android.support包,用androidx的自行修改相关包名。
3.你可能在其他地方见到not()、allOf()等方法,这些也是静态方法,位于类 org.hamcrest.Matchers
中,导入即可。
基本使用
1.请自行添加相关注解@RunWith(AndroidJUnit4.class)和@Test等,并且请添加以下成员到测试类中
@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class);
这样是为了获取Activity使得测试可进行。此后你可以mActivityRule.getActivity()
来获取Activity,这个和实际的Activity没有什么差异,你甚至可以直接调用Activity.findViewById()
来获取相关控件以操作。但实际上不推荐,除非有些问题只用单元测试的确难以解决。
2.withId(R.id.xxx)
,根据id返回一个ViewMatchers,代表着该控件。
withText("admin")
,根据文本内容返回一个ViewMatchers,代表着该控件。
3.onView(withId(R.id.xxx))
,返回一个ViewInteraction,这样你就可以与该对象代表的控件进行点击、输入文本等交互
4.onView(withId(R.id.xxx)).perform(click());
模拟点击该控件
5.onView(withId(R.id.xxx)).perform(typeText("admin"));
模拟逐个字母来输入文本“admin”。注意,他是逐个字母来输入,因此中文是无法输入的,请使用replaceText("中文")
代替。replaceText是直接替换文本进去,如果你需要类似typeText的来输入中文,请结合Thread.sleep()来设置延时以达到目的。
6.onView(withId(R.id.xxx)).check(matches(isDisplayed()));
isDisplayed()返回一个ViewMatchers,matches()返回一个ViewAssertions,最后check()检测是否符合断言。
简单来讲就是检测withId(R.id.xxx)
与isDisplayed()
是否是等价的ViewMatchers。注意用词是等价不是相等,这里如果该控件可见,将返回真,否则返回假。类似地,onView(withId(R.id.xxx)).check(matches(withText("admin")))
可以判断对应控件内容是否是“admin”
AdapterView控件的单元测试
以ListView举例。模拟点击ListView的第0项:
onData(is(instanceOf(String.class)))
.inAdapterView(withId(R.id.list_view))
.atPosition(0)
.perform(click());
英文好的话上面的不难理解。唯一较大差异的就在"String.class"这里,这里只要填上
方法“getItem(int position)”返回的对象的类型即可。如果你用的是SimpleAdapter或其他,请自行弄明白其getItem将返回什么类型的对象。
当然,方法是多样的,也可以更加简单的:
onData(anything())
.inAdapterView(withId(R.id.list_view))
.atPosition(0)
.perform(click());
RecycleView 和 NavigationView
这些较新的android.support(androidx)包的控件,google提供了更方便的方法,只要导入一个包即可:androidTestImplementation group: 'com.android.support.test.espresso', name: 'espresso-contrib', version: '3.0.0'
具体使用很简单,直接用对应类的静态方法就行:
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
onView(withId(R.id.navigation_view))
.perform(NavigationViewActions.navigateTo(R.id.menu_item));
注意一些坑
1.check(matches(not(isDisplayed())))
和check(doesNotExist())
有一定出入。
官方原话:If the view is gone from the view hierarchy—which can happen when an action caused a transition to another activity—you should use ViewAssertions.doesNotExist()。
如果你只是把控件设置成INVISIBLE,第一种方法是可以的。但是,如果你是把该控件直接从其父控件中removeView()掉,使得其不在当前activity的视图层次,那么你应该使用第二种方法来断言其不出现。