Espresso测试框架
Espresso是Google官方提供并推荐的Android测试库,它是一个AndroidJunit单元测试库,用于Android仪器化测试,即需要运行到设备或模拟器上进行测试。Espresso是意大利语“咖啡”的意思,它的最大的优势是可以实现UI自动化测试,设计者的意图是想实现“喝杯咖啡的功夫”就可以等待自动测试完成。通常我们需要手动点击测试的UI功能,利用这个库可以自动为你实现。
添加依赖:
dependencies {androidTestImplementation 'com.android.support.test:runner:1.0.2'androidTestImplementation 'com.android.support.test:rules:1.0.2'androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'androidTestImplementation "com.android.support.test.espresso:espresso-contrib:3.0.2"androidTestImplementation "com.android.support.test.espresso:espresso-idling-resource:3.0.2"androidTestImplementation "com.android.support.test.espresso:espresso-intents:3.0.2"
}
android {defaultConfig {....testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"}
}
目前使用AS创建项目的时候,会自动为你添加Espresso的依赖。
官方Doc入口:Espresso basics
Espresso 由以下三个基础部分组成:
- ViewMatchers 在当前View层级去匹配指定的View
- ViewActions 执行Views的某些行为
- ViewAssertions 检查Views的某些状态
获取View
//根据id匹配
onView(withId(R.id.my_view))
//根据文本匹配
onView(withText("Hello World!"))
执行View的行为
//点击
onView(...).perform(click());
//输入文本
onView(...).perform(typeText("Hello World"), closeSoftKeyboard());
//滑动(使屏幕外的view显示) 点击
onView(...).perform(scrollTo(), click());
//清除文本
onView(...).perform(clearText());
一些方法含义:
方法名 | 含义 |
---|---|
click() | 点击view |
clearText() | 清除文本内容 |
swipeLeft() | 从右往左滑 |
swipeRight() | 从左往右滑 |
swipeDown() | 从上往下滑 |
swipeUp() | 从下往上滑 |
click() | 点击view |
closeSoftKeyboard() | 关闭软键盘 |
pressBack() | 按下物理返回键 |
doubleClick() | 双击 |
longClick() | 长按 |
scrollTo() | 滚动 |
replaceText() | 替换文本 |
openLinkWithText() | 打开指定超链 |
typeText() | 输入文本 |
检验View内容
//检验View的本文内容是否匹配“Hello World!”
onView(...).check(matches(withText("Hello World!")));
//检验View的内容是否包含“Hello World!”onView(...).check(matches(withText(containsString("Hello World!"))));
//检验View是否显示
onView(...).check(matches(isDisplayed()));
//检验View是否隐藏
onView(...).check(matches(not(isDisplayed())));
其中check中匹配的内容可以嵌套任何Matchers类的函数。
简单例子
下面建一个模拟登陆的页面,xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_margin="30dp"android:orientation="vertical"><EditTextandroid:id="@+id/edit_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:inputType="text"android:maxLines="1"android:hint="请输入用户名"android:textSize="16sp"android:textColor="@android:color/black" /><EditTextandroid:id="@+id/edit_password"android:layout_width="match_parent"android:layout_height="wrap_content"android:inputType="textVisiblePassword"android:maxLines="1"android:hint="请输入密码"android:textSize="16sp"android:textColor="@android:color/black" /><Buttonandroid:id="@+id/btn_login"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="登录"android:textSize="17sp"android:textColor="@android:color/black"/>
</LinearLayout>
Activity代码:
public class LoginActivity extends Activity implements View.OnClickListener {private EditText mNameEdit;private EditText mPasswordEdit;private Button mLoginBtn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);initView();}private void initView() {mNameEdit = (EditText) findViewById(R.id.edit_name);mPasswordEdit = (EditText) findViewById(R.id.edit_password);mLoginBtn = (Button) findViewById(R.id.btn_login);mLoginBtn.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_login:login();break;default:break;}}private void login() {if (TextUtils.isEmpty(mNameEdit.getText().toString())) {Toast.makeText(this, "用户名为空", Toast.LENGTH_SHORT).show();return;}if (TextUtils.isEmpty(mPasswordEdit.getText().toString())) {Toast.makeText(this, "密码为空", Toast.LENGTH_SHORT).show();return;}if (mPasswordEdit.getText().length() < 6) {Toast.makeText(this, "密码长度小于6", Toast.LENGTH_SHORT).show();return;}mLoginBtn.setText("登录成功");Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();}}
测试类代码:
@RunWith(AndroidJUnit4.class)
@LargeTest//允许测试需要较大消耗
public class LoginActivityTest {//指定测试的目标Activity页面@Rulepublic ActivityTestRule mActivityTestRule = new ActivityTestRule<>(LoginActivity.class);@Testpublic void testLogin() {//验证是否显示onView(withId(R.id.btn_login)).check(matches(isDisplayed()));//不输入任何内容,直接点击登录按钮onView(withId(R.id.btn_login)).perform(click());//onView(allOf(withId(R.id.btn_login), isDisplayed())).perform(click());//只输入用户名onView(withId(R.id.edit_name)).perform(typeText("admin"), closeSoftKeyboard());onView(withId(R.id.btn_login)).perform(click());onView(withId(R.id.edit_name)).perform(clearText());//同时输入用户名和密码,但是密码格式不正确onView(withId(R.id.edit_name)).perform(typeText("admin"));onView(withId(R.id.edit_password)).perform(typeText("123"), closeSoftKeyboard());onView(withId(R.id.btn_login)).perform(click());onView(withId(R.id.edit_name)).perform(clearText());onView(withId(R.id.edit_password)).perform(clearText());//输入正确的用户名和密码onView(withId(R.id.edit_name)).perform(typeText("admin"));onView(withId(R.id.edit_password)).perform(typeText("123456"), closeSoftKeyboard());onView(withId(R.id.btn_login)).perform(click());//验证内容onView(withId(R.id.btn_login)).check(matches(withText("登录成功")));onView(withId(R.id.edit_name)).check(matches(withText("admin")));onView(withId(R.id.edit_password)).check(matches(withText("123456")));}}
运行测试
当我们右键运行测试方法时,AS会为我们生成两个apk进行安装:
当这两个apk安装到手机上以后,Espresso会根据测试用例自动打开LoginActivity, 然后执行输入点击等一系列操作,整个过程是全自动的,不需要你去手动操作。
当运行完毕,会关闭LoginActivity,如果没有出错,控制台会显示测试通过:
这里有一个地方需要注意的是,如果手机用的是搜狗输入法最好先切换成系统输入法,必须保证只有全键盘的输入法,否则输入的时候会有问题,比如搜狗的输入“admin”时,默认第一个字母会大写, 结果变成“Admin”, 导致测试结果不匹配。
如果你想输入中文目前还做不到通过键盘的方式输入(因为Espresso不知道哪些按键可以输出你要的文字),所以中文只能用replaceText
的方法:
onView(withId(R.id.edit_name)).perform(replaceText("小明"));
建议所有的输入都使用replaceText()
的方式,这样就不用担心键盘输入法的问题了。
验证Toast
上面测试代码如果要验证Toast是否弹出,可以这么写:
@Testpublic void testLogin() throws Exception {//不输入任何内容,直接点击登录按钮onView(withId(R.id.btn_login)).perform(click());//验证是否弹出文本为"用户名为空"的ToastonView(withText("用户名为空")).inRoot(withDecorView(not(is(mActivityTestRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));Thread.sleep(1000);//只输入用户名onView(withId(R.id.edit_name)).perform(typeText("admin"), closeSoftKeyboard());onView(withId(R.id.btn_login)).perform(click());//验证"密码为空"的ToastonView(withText("密码为空")).inRoot(withDecorView(not(is(mActivityTestRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));onView(withId(R.id.edit_name)).perform(clearText());Thread.sleep(1000);//同时输入用户名和密码,但是密码格式不正确onView(withId(R.id.edit_name)).perform(typeText("admin"));onView(withId(R.id.edit_password)).perform(typeText("123"), closeSoftKeyboard());onView(withId(R.id.btn_login)).perform(click());//验证"密码长度小于6"的ToastonView(withText("密码长度小于6")).inRoot(withDecorView(not(is(mActivityTestRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));//........}
因为Toast的显示需要一定的时间,所以如果在一个测试方法中连续的测试Toast这里需要调用Thread.sleep(1000)
休眠等待一段时间再继续,否则会检测不到。当然最好的做法是将每一个测试用例放到一个单独的单元测试方法中,这样可以不用等待。
验证Dialog
验证Dialog的方法跟Toast其实一样的。我们在Activity中back键按下的时候弹出一个提醒弹窗:
@Overridepublic void onBackPressed() {new AlertDialog.Builder(this).setTitle("提示").setMessage("确认退出应用吗").setPositiveButton("确认", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialogInterface, int i) {dialogInterface.dismiss();finish();}}).setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialogInterface, int i) {dialogInterface.dismiss();}}).create().show();}
测试代码:
@Testpublic void testDialog() throws Exception {//按下返回键pressBack();//验证提示弹窗是否弹出onView(withText(containsString("确认退出应用吗"))).inRoot(withDecorView(not(is(mActivityTestRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));//点击弹窗的确认按钮onView(withText("确认")).inRoot(withDecorView(not(is(mActivityTestRule.getActivity().getWindow().getDecorView())))).perform(click());Assert.assertTrue(mActivityTestRule.getActivity().isFinishing());}
验证目标Intent
修改代码将上面LoginActivity中点击登录按钮的时候跳转到另一个Activity:
@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_login://login();Intent intent = new Intent(this, HomeActivity.class);intent.putExtra("id", "123");startActivity(intent);break;default:break;}}
测试代码:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class LoginActivityTest2 {@Rulepublic IntentsTestRule mIntentTestRule = new IntentsTestRule<>(LoginActivity.class);@Beforepublic void setUp() {Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, null);//当启动目标的Activity时,设置一个模拟的ActivityResultintending(allOf(toPackage(InstrumentationRegistry.getTargetContext().getPackageName()),hasComponent(hasShortClassName("HomeActivity")))).respondWith(result);}@Testpublic void testJump() throws Exception {onView(withId(R.id.btn_login)).perform(click());//目标Intent是否启动了intended(allOf(toPackage(InstrumentationRegistry.getTargetContext().getPackageName()),hasComponent(HomeActivity.class.getName()),hasExtra("id", "123")));}
}
这里需要把ActivityTestRule
换成IntentsTestRule
,IntentsTestRule
本身是继承ActivityTestRule
的。intending
表示触发一个Intent的时候,类似于Mockito.when
语法,你可以通过intending(matcher).thenRespond(myResponse)
的方式来设置当触发某个intent的时候模拟一个返回值,intended
表示是否已经触发某个Intent, 类似于Mockito.verify(mock, times(1))
。
其中Intent
的匹配项可以通过IntentMatchers
类提供的方法:
更多Intent的匹配请参考官方:https://developer.android.google.cn/training/testing/espresso/intents
访问Activity
获取ActivityTestRule指定的Activity实例:
@Testpublic void test() throws Exception {LoginActivity activity = mActivityTestRule.getActivity();activity.login();}
获取前台Activity实例(如启动另一个Activity):
@Testpublic void navigate() {onView(withId(R.id.btn_login)).perform(click());Activity activity = getActivityInstance();assertTrue(activity instanceof HomeActivity);// do more}public Activity getActivityInstance() {final Activity[] activity = new Activity[1];InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {@Overridepublic void run() {Activity currentActivity = null;Collection resumedActivities =ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);if (resumedActivities.iterator().hasNext()) {currentActivity = (Activity) resumedActivities.iterator().next();activity[0] = currentActivity;}}});return activity[0];}
手动启动Activity
默认测试方法是会直接启动Activity的,也可以选择自己启动Activity并通过Intent传值
@RunWith(AndroidJUnit4.class)
@LargeTest
public class LoginActivityTest3 {@Rulepublic ActivityTestRule mRule = new ActivityTestRule<>(LoginActivity.class, true, false);@Testpublic void start() throws Exception {Intent intent = new Intent();intent.putExtra("name", "admin");mRule.launchActivity(intent);onView(withId(R.id.edit_name)).check(matches(withText("admin")));}}
提前注入Activity的依赖
有时Activity在创建之前会涉及到一些第三方库的依赖,这时直接跑测试方法会报错。
@Overrideprotected void onCreate(Bundle savedInstanceState) {Log.e("AAA", "onCreate: " + ThirdLibrary.instance.name);super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);initView();checkPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CALL_PHONE);}
通过ActivityTestRule
可以在Activity启动之前或之后做一些事情,我们可以选择在Activity启动之前创建好这些依赖:
@Rulepublic ActivityTestRule<LoginActivity> mActivityTestRule = new ActivityTestRule<LoginActivity>(LoginActivity.class){@Overrideprotected void beforeActivityLaunched() {//在目标Activity启动之前执行,会在每个测试方法之前运行,包括@BeforeThirdLibrary.instance = new ThirdLibrary();}@Overrideprotected void afterActivityLaunched() {//在目标Activity启动之后执行,会在任意测试方法之前运行,包括@Before}@Overrideprotected void afterActivityFinished() {//启动Activity结束以后执行,会在任意测试方法之后运行,包括@After}};
这样在测试方法之前会先执行beforeActivityLaunched创建好Activity需要的依赖库。
添加权限
Espresso可以在运行测试方法前手动为应用授予需要的权限:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class LoginActivityTest {public static String[] PERMISSONS_NEED = new String[] {Manifest.permission.CALL_PHONE,Manifest.permission.WRITE_EXTERNAL_STORAGE};@Rulepublic GrantPermissionRule grantPermissionRule = GrantPermissionRule.grant(PERMISSONS_NEED);@Testpublic void testPermission() throws Exception {LoginActivity activity = mActivityTestRule.getActivity();Assert.assertEquals(PackageManager.PERMISSION_GRANTED,ContextCompat.checkSelfPermission(activity, PERMISSONS_NEED[0]));Assert.assertEquals(PackageManager.PERMISSION_GRANTED,ContextCompat.checkSelfPermission(activity, PERMISSONS_NEED[1]));}
}
通过@Rule
注解指定一个GrantPermissionRule
即可。GrantPermissionRule指定目标页面需要的权限,在测试方法运行前会自动授权。
测试View的位置
直接看图:
RecyclerView点击Item
点击某个Item:
@Testpublic void clickItem() {onView(withId(R.id.rv_person)).perform(actionOnItemAtPosition(10, click()));//或者:onView(withId(R.id.rv_person)).perform(actionOnItem(hasDescendant(withText("姓名10")), click()));//验证toast弹出onView(withText("姓名10")).inRoot(withDecorView(not(is(mActivityTestRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));}
点击某个Item的子View,可以通过自定义ViewAction实现:
@Testpublic void clickChildItem() {onView(withId(R.id.rv_person)).perform(actionOnItemAtPosition(10, clickChildViewWithId(R.id.tv_name)));//验证toast弹出onView(withText("onItemChildClick")).inRoot(withDecorView(not(is(mActivityTestRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));}public static ViewAction clickChildViewWithId(final int id) {return new ViewAction() {@Overridepublic Matcher<View> getConstraints() {return null;}@Overridepublic String getDescription() {return "Click on a child view with specified id.";}@Overridepublic void perform(UiController uiController, View view) {View v = view.findViewById(id);v.performClick();}};}
RecyclerViewActions类提供的一些可以操作RecyclerView的方法:
方法名 | 含义 |
---|---|
scrollTo | 滚动到匹配的view |
scrollToHolder | 滚动到匹配的viewholder |
scrollToPosition | 滚动到指定的position |
actionOnHolderItem | 在匹配到的view holder中进行操作 |
actionOnItem | 在匹配到的item view上进行操作 |
actionOnItemAtPosition | 在指定位置的view上进行操作 |
ListView点击Item
假设ListView的Adapter中的Item的定义如下:
public static class Item {private final int value;public Item(int value) {this.value = value;}public String toString() {return String.valueOf(value);}
}
点击某个item:
@Test
public void clickItem() {onData(withValue(27)).inAdapterView(withId(R.id.list)).perform(click());//Do the assertion here.
}public static Matcher<Object> withValue(final int value) {return new BoundedMatcher<Object,MainActivity.Item>(MainActivity.Item.class) {@Override public void describeTo(Description description) {description.appendText("has value " + value);}@Override public boolean matchesSafely(MainActivity.Item item) {return item.toString().equals(String.valueOf(value));}};
}
点击某个item的子View:
onData(withItemContent("xxx")).onChildView(withId(R.id.tst)).perform(click());
更多列表点击的介绍可以参考官方:
https://developer.android.google.cn/training/testing/espresso/lists
自定义Matcher
匹配EditText的hint为例:
/*** A custom matcher that checks the hint property of an {@link android.widget.EditText}. It* accepts either a {@link String} or a {@link org.hamcrest.Matcher}.*/
public class HintMatcher {static Matcher<View> withHint(final String substring) {return withHint(is(substring));}static Matcher<View> withHint(final Matcher<String> stringMatcher) {checkNotNull(stringMatcher);return new BoundedMatcher<View, EditText>(EditText.class) {@Overridepublic boolean matchesSafely(EditText view) {final CharSequence hint = view.getHint();return hint != null && stringMatcher.matches(hint.toString());}@Overridepublic void describeTo(Description description) {description.appendText("with hint: ");stringMatcher.describeTo(description);}};}
}
测试代码:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class HintMatchersTest {private static final String COFFEE_ENDING = "coffee?";private static final String COFFEE_INVALID_ENDING = "tea?";@Rulepublic ActivityTestRule mActivityTestRule = new ActivityTestRule<>(LoginActivity.class);/*** Uses a custom matcher {@link HintMatcher#withHint}, with a {@link String} as the argument.*/@Testpublic void hint_isDisplayedInEditText() {String hintText = InstrumentationRegistry.getContext().getResources().getString(R.string.hint_edit_name);onView(withId(R.id.edit_name)).check(matches(HintMatcher.withHint(hintText)));}/*** Same as above but using a {@link org.hamcrest.Matcher} as the argument.*/@Testpublic void hint_endsWith() {// This check will probably fail if the app is localized and the language is changed. Avoid string literals in code!onView(withId(R.id.edit_name)).check(matches(HintMatcher.withHint(anyOf(endsWith(COFFEE_ENDING), endsWith(COFFEE_INVALID_ENDING)))));}}
IdlingResource的使用
虽然单元测试不建议处理异步的操作,但是Espresso也提供了这样的支持,因为实际中还是会有很多地方会用到的,常见的场景有异步网络请求、异步IO数据操作等。Espresso提供的解决异步问题的方案就是IdlingResource
。
例如Activity启动后加载网络图片需要经过一段时间再更新ImageView显示:
public class LoadImageActivity extends Activity {private ImageView mImageView;private boolean mIsLoadFinished;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_load_image);mImageView = (ImageView) findViewById(R.id.iv_test);final String url = "http://pic21.photophoto.cn/20111019/0034034837110352_b.jpg";Glide.with(this).load(url).into(new SimpleTarget<Drawable>() {@Overridepublic void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {mImageView.setImageDrawable(resource);mImageView.setContentDescription(url);mIsLoadFinished = true;}@Overridepublic void onLoadFailed(@Nullable Drawable errorDrawable) {super.onLoadFailed(errorDrawable);mIsLoadFinished = true;}});}public boolean isLoadFinished() {return mIsLoadFinished;}
}
这时测试用例验证必须等待加载完以后才能进行。我们需要定义一个IdlingResource
接口的实现类:
public class SimpleIdlingResource implements IdlingResource {private volatile ResourceCallback mCallback;private LoadImageActivity mLoadImageActivity;public SimpleIdlingResource(LoadImageActivity activity) {mLoadImageActivity = activity;}@Overridepublic String getName() {return this.getClass().getName();}@Overridepublic boolean isIdleNow() {if (mLoadImageActivity != null && mLoadImageActivity.isLoadFinished()) {if (mCallback != null) {mCallback.onTransitionToIdle();}return true;}return false;}@Overridepublic void registerIdleTransitionCallback(ResourceCallback callback) {mCallback = callback;}
}
IdlingResource
接口的三个方法含义:
getName()
方法返回的String是作为注册回调的Key,所以要确保唯一性registerIdleTransitionCallback()
的参数ResourceCallback
会用做isIdleNow()
时候的回调isIdleNow()
是否已经处于空闲状态,这里是通过查询Activity的一个标志位来判断的
定义好IdlingResource
接口的实现类之后,我们需要在测试类当中的测试方法执行之前和之后进行注册和反注册,我们可以在@Before
方法中进行注册,而在@After
方法中进行反注册:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class LoadImageActivityTest {@Rulepublic ActivityTestRule<LoadImageActivity> mActivityRule = new ActivityTestRule<>(LoadImageActivity.class);SimpleIdlingResource mIdlingResource;@Beforepublic void setUp() throws Exception {LoadImageActivity activity = mActivityRule.getActivity();mIdlingResource = new SimpleIdlingResource(activity);IdlingRegistry.getInstance().register(mIdlingResource);}@Afterpublic void tearDown() throws Exception {IdlingRegistry.getInstance().unregister(mIdlingResource);}@Testpublic void loadImage() throws Exception {String url = "http://pic21.photophoto.cn/20111019/0034034837110352_b.jpg";onView(withId(R.id.iv_test)).check(matches(withContentDescription(url)));}
}
这时运行测试方法loadImage(),Espresso会首先启动LoadImageActivity,然后一直等待SimpleIdlingResource中的状态变为idle时,才会去真正执行loadImage()测试方法里的代码,达到了同步的效果目的。
同样,其他的异步操作都是类似的处理。
Espresso UI Recorder
Espresso在Run菜单中提供了一个Record Espresso Test
功能,选择之后可以将用户的操作记录下来并转成测试代码。
选择之后,会弹出一个记录面板:
接下来你在手机上针对应用的每一步操作会被记录下来,最后点击Ok就会自动生成刚才操作的case代码了。试了一下这个功能的想法还是很好的,可是操作起来太卡了,不太实用。
WebView的支持
为方便测试Espresso专门为WebView提供了一个支持库,具体参考官方介绍:Espresso Web
多进程的支持
单元测试很少用,直接看官方的介绍: Multiprocess Espresso
Accessibility的支持
直接看官方的介绍:Accessibility checking
More Espresso Demo
更多Espresso的Demo可以参考官方的介绍:Additional Resources for Espresso 基本上demo都是在Github上面的, 本文中找不到的例子可以到里面找找看。
Espresso备忘清单
最后再来一张Espresso备忘清单,方便速查:
Espresso踩坑
目前官方Espresso
最新的版本是3.1.0
,支持androidX(API 28 Android 9.0)
, 我在测试的时候由于AS还没有升级(用的是3.1.4的版本),所以新建项目的时候,默认添加的还是3.0.2的版本,如果compileSdkVersion
和targetSdkVersion
都是27,并且所有的support
依赖库都是27.1.1版本的则没有问题,但是有一些第三方库还是support 26.1.0的,所以会出现下面的问题:
这个问题真的很难搞,折腾了好久,采用下面的方法,强行指定support版本库为26.1.0:
configurations.all {resolutionStrategy.force 'com.android.support:design:26.1.0'resolutionStrategy.force 'com.android.support:support-annotations:26.1.0'resolutionStrategy.force 'com.android.support:recyclerview-v7:26.1.0'resolutionStrategy.force 'com.android.support:support-v4:26.1.0'resolutionStrategy.force 'com.android.support:appcompat-v7:26.1.0'resolutionStrategy.force 'com.android.support:cardview-v7:26.1.0'resolutionStrategy.force 'com.android.support:support-core-utils:26.1.0'resolutionStrategy.force 'com.android.support:support-compat:26.1.0'
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])testImplementation 'junit:junit:4.12'androidTestImplementation 'com.android.support.test:runner:1.0.2'androidTestImplementation 'com.android.support.test:rules:1.0.2'androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'androidTestImplementation "com.android.support.test.espresso:espresso-contrib:3.0.2"androidTestImplementation "com.android.support.test.espresso:espresso-idling-resource:3.0.2"androidTestImplementation "com.android.support.test.espresso:espresso-intents:3.0.2"implementation deps.common_recycleradapterimplementation deps.support.recyclerview
}
这样可以通过,但不是最好的解决办法,最好的办法是所有的依赖库support版本都升级到27.1.1,但是有些库是别人提供的并且没有升级的话就很麻烦了
所有代码已上传至Gitee。希望大家多多点赞支持!有什么问题及建议也可以提Issues,让我们将他慢慢的完善起来。
Espresso测试框架相关推荐
- Android Espresso 测试框架探究
1 简介 Espresso 是谷歌官方实现的一个测试框架,根据官方文档,该框架主要能实现如下的功能. 查找一个view是否显示 在一个view上触发一个动作 查询一个view中是否符合一个断言 使用 ...
- Espresso测试框架的使用
文章目录 获取View 执行View的行为 检验View内容 简单例子 验证Toast 验证Dialog 验证目标Intent 访问Activity 提前注入Activity的依赖 添加权限 测试Vi ...
- 在Android Studio环境下使用ESPRESSO 测试框架进行UI测试
1.首先,在后缀为AndroidTest的文件夹内建立一个MainActivityInstrumentedTest的Java文件, package com.example.pj.git; import ...
- Android UI 测试框架Espresso详解
Android UI 测试框架Espresso详解 1. Espresso测试框架 2.提供Intents Espresso 2.1.安装 2.2.为Espresso配置Gradle构建文件 2.3. ...
- 5个最佳的Android测试框架
2019独角兽企业重金招聘Python工程师标准>>> 谷歌的Android生态系统正在不断地迅速扩张.有证据表明,新的移动OEM正在攻陷世界的每一个角落,不同的屏幕尺寸.ROM / ...
- Android开源测试框架
Google Espresso Espresso是一个新工具,相对于其他工具,API更加精确.并且规模更小.更简洁并且容易学习.它 最初是2013年GTAC大会上推出的,目标是让开发者写出更简洁的 ...
- 关于Android的自动化测试,你需要了解的5个测试框架
Appium Appium是一个开源的移动测试工具,支持iOS和Android,它可以用来测试任何类型的移动应用(原生.网络和混合).作为一个跨平台的工具,你可以在不同的平台上运行相同的测试.为了实现 ...
- Android自动化测试,5个必备的测试框架
Appium Appium是一个开源的移动测试工具,支持iOS和Android,它可以用来测试任何类型的移动应用(原生.网络和混合).作为一个跨平台的工具,你可以在不同的平台上运行相同的测试.为了实现 ...
- 软件自动测试框架,软件自动化测试框架的研究和实现
摘要: 软件自动化测试是软件工程领域的一项重要课题.随着软件工程理论的不断发展,软件自动化测试在理论上也不断达到新的高度.目前最为成熟的软件自动化测试技术是使用自动测试框架来指导自动化测试的实现.迄今 ...
最新文章
- 万字长文|深入理解XDP全景指南
- Node_初步了解(4)小爬虫
- linux_adduser
- Phoenix的数据类型和操作符、函数
- DWM1000 收发RXLED TXLED控制代码修改
- confluence统计_【漏洞预警】confluence远程代码执行漏洞(CVE-2019-3396)
- linux查看耗费流量的进程--iftop
- python爬虫淘宝视频_python爬虫视频教程:一篇文章教会你用Python爬取淘宝评论数据...
- 三种css样式应用方式,CSS样式三种形式
- 绩效管理实务与管理效率提升-王晓耕老师
- 大数据-zookeeper(上)
- (windows) node-sass 安装报错
- idea中Entity实体中报错:cannot resolve column/table/...解决办法。
- Pandas入门超详细教程,看了超简单
- 什么是modbus通信协议?
- POJO有哪些要求?
- 微信图像接口 html,图像接口
- 计算机专业助我成长作文700,你让我成长作文700字
- Qt多语言实现和动态切换(国际化)
- 简单实现前端选择上传图片并显示略缩图