设置测试环境
-
为了避免测试不稳定,我们强烈建议您在用于测试的虚拟或物理设备上关闭系统动画。在您的设备上,在设置 > 开发者选项下,停用以下三项设置:
- 窗口动画缩放
- 过渡动画缩放
- Animator 时长缩放
Tips:部分手机可能无法正常运行会报找不到Intent错误,这种情况请更换测试机
-
添加依赖
androidTestImplementation 'androidx.test:core:' + rootProject.coreVersion
androidTestImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
androidTestImplementation 'androidx.test:runner:' + rootProject.runnerVersion
androidTestImplementation 'androidx.test.espresso:espresso-core:' + rootProject.espressoVersion
androidTestImplementation 'androidx.fragment:fragment-testing:' + rootProject.androidxFragmentVersion
androidTestImplementation 'com.google.truth:truth:' + rootProject.truthVersion
androidTestImplementation 'org.robolectric:annotations:' + rootProject.robolectricVersion
androidTestImplementation 'androidx.test.espresso:espresso-intents:' + rootProject.espressoVersion
> 对应版本号 也可以自行更换别的版本号
androidxFragmentVersion = "1.3.6"
coreVersion = "1.4.1-alpha04"
extJUnitVersion = "1.1.4-alpha04"
runnerVersion = "1.5.0-alpha01"
rulesVersion = "1.4.1-alpha04"
espressoVersion = "3.5.0-alpha04"
robolectricVersion = "4.6.1"
truthVersion = "1.1.3"
-
将以下代码行添加到同一 build.gradle 文件的 android.defaultConfig 中:
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-
一般新建的项目会有两个文件夹存放测试代码的地方
要是想要将它们放在一个文件夹下面也可以配置
相关文档
- 官方Demo github地址
- Espresso谷歌文档中文版
- Espresso文档
- Espresso自定义匹配器以及各种常用Api释义
- Espresso模拟返回相机或者相册结果
- IdlingResource介绍
- Espresso Demo合集
示例Demo
这个Demo有两个按钮,按钮(CHANGE TEXT)的作用就是把那个EditText里面的文本替换上面那个Hellow Espressol的文本
接下来运行第一个测试方法看看效果,顺带说一下,每个测试方法运行完成后会退出app的,这种情况不是闪退,先看测试通过的,测试代码的意思就是寻找一个id为editTextUserInput的控件,给它输入文案,然后点击CHANGE TEXT按钮替换文案,最后再看id 为textToBeChanged的TextView的文案是否跟替换的文案一致,一致就通过,不一致就失败
@Test
public void changeText_sameActivity() throws InterruptedException {
// Type text and then press the button.
onView(withId(R.id.editTextUserInput))
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
onView(withId(R.id.changeTextBt)).perform(click());
// Check that the text was changed.
onView(withId(R.id.textToBeChanged)).check(matches(withText(STRING_TO_BE_TYPED)));
}
咱们再看一下测试不通过的,不通过是因为我输入的是111111,它跟比对的值10086这个不相等,所以就测试不通过
@Test
public void changeText_sameActivity() throws InterruptedException {
Thread.sleep(5000);
// Type text and then press the button.
onView(withId(R.id.editTextUserInput))
.perform(typeText("111111"), closeSoftKeyboard());
onView(withId(R.id.changeTextBt)).perform(click());
// Check that the text was changed.
onView(withId(R.id.textToBeChanged)).check(matches(withText(STRING_TO_BE_TYPED)));
}
常用Api介绍
UI测试的启动入口是根据这一行代码来启动的,启动方式也有区别,一种是加了Rule注解是属于rules库里面的启动方式,另一种是test.core库里面的
@Rule public ActivityScenarioRule activityScenarioRule
= new ActivityScenarioRule<>(MainActivity.class);
ActivityScenario scenario = ActivityScenario.launch(MainActivity.class);
使用onView或者onData查找视图的时候会出现找不到视图的情况,明明当前页面有这个id或者有这个文本的View,就是找不着,这是因为代码的执行速度比界面更快,所以代码执行的时候会出现View还没加载出来的情况,这样这个测试就失败了,关于这个问题,谷歌官方文档有解决办法
IdlingResource
1.停止指定的时间 不需要注入
2.根据真实耗时时间回调 需要注入测试代码到源码中
方法一 实现IdlingResource ,重写 isIdleNow(),这种方式相当于是给测试添加Thread.sleep
class ElapsedTimeIdlingResource implements IdlingResource {
private final long startTime;
private final long waitingTime;
private ResourceCallback resourceCallback;
public ElapsedTimeIdlingResource(long waitingTime) {
this.startTime = System.currentTimeMillis();
this.waitingTime = waitingTime;
}
@Override
public String getName() {
return ElapsedTimeIdlingResource.class.getName() + ":" + waitingTime;
}
@Override
public boolean isIdleNow() {
long elapsed = System.currentTimeMillis() - startTime;
boolean idle = (elapsed >= waitingTime);
if (idle) {
resourceCallback.onTransitionToIdle();
}
return idle;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
}
在需要后台耗时工作测试代码前面添加如下代码,也可在@Before注解的方法下添加,记得在@After取消注册,节省资源
idlingResource = new ElapsedTimeIdlingResource(1000 * 3);
Espresso.registerIdlingResources(idlingResource);
方法二注入待测类,这个方法需要改项目的源代码,在源代码添加测试代码,在耗时任务之前mIdlingResource.setIdleState(false);耗时任务结束后mIdlingResource.setIdleState(true);
public class SimpleIdlingResource implements IdlingResource {
@Nullable
private volatile ResourceCallback mCallback;
// Idleness is controlled with this boolean.
private AtomicBoolean mIsIdleNow = new AtomicBoolean(true);
@Override
public String getName() {
return this.getClass().getName();
}
@Override
public boolean isIdleNow() {
return mIsIdleNow.get();
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
mCallback = callback;
}
/**
* Sets the new idle state, if isIdleNow is true, it pings the {@link ResourceCallback}.
*
* @param isIdleNow false if there are pending operations, true if idle.
*/
public void setIdleState(boolean isIdleNow) {
mIsIdleNow.set(isIdleNow);
if (isIdleNow && mCallback != null) {
mCallback.onTransitionToIdle();
}
}
在待测类中需要定义该方法
/**
* Only called from test, creates and returns a new {@link SimpleIdlingResource}.
*/
@VisibleForTesting
@NonNull
public IdlingResource getIdlingResource() {
if (mIdlingResource == null) {
mIdlingResource = new SimpleIdlingResource();
}
return mIdlingResource;
}
Espresso 踩坑或一些不常用的方法示例
- onData()获取ListView或者GridView的item的方法,atPosition的索引是从0开始,我这是因为项目中的ListView封装了下拉刷新,刷新占在第一个位置,所以Item我这是从1开始
onData(allOf())
.inAdapterView(withId(R.id.lv_content)) // listview的id
.atPosition(1) // 所在位置
.onChildView(withId(childViewId)) // item中子控件id
.check(ViewAssertions.matches(isDisplayed()));
-
自动生成UI测试代码
运行以后在手机上执行对应操作就可以生成这样的一个界面,然后点个OK就会生成测试代码了,生成的测试代码点击运行后就会执行跟你刚刚的操作,不过项目复杂度高的话生成出的测试用例也会有失败的情况,一般生成的代码仅供参考
-
导出测试结果