小组2018的工作重心可能会放在app性能优化这一块。千分之八的崩溃率被大佬们屌了无数次。先定一个千分之三的小目标。所以接下来的工作要围绕稳定性和流畅性来展开。那么无可避免的会经常使用到android自动化测试了。
下面正式开始
首先第一步要做的就是集成Espresso测试环境,非常简单,在你要测试的Module的gradle里添加如下依赖:
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
}
androidTestCompile 'com.android.support.test:rules:1.0.1'
举个栗子:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityInstrumentationTest {
@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(
MainActivity.class);
@Test
public void sayHello(){
onView(withText("Say hello!")).perform(click());
onView(withId(R.id.textView)).check(matches(withText("Hello, World!")));
}
}
上面这段代码是android官网上面的,关于Espresso的原理我就不说了,我也讲不来,强行讲的话,也讲的太肤浅.想了解原理的可以去Google一把.
那么我们首先关注两个注解:
@Rule:
顾名思义,测试规则,官方的解释是你可以在该注解下引用一个规则或定义一个方法,而你引用或定义的就是测试规则,这样说可能不是很清楚,可以看该注解下面的一句话:
public ActivityTestRule mActivityRule = new ActivityTestRule<>(
MainActivity.class);
这句话就定义了一个测试规则,可以看到构造方法的参数里指定了一个 MainActivity.class,具体的体现就是当你运行这段测试代码时,app将会直接打开 MainActivity界面然后进行你所定义的测试用例.所以当你想直接测试某个界面时,你可以把那个界面填到这个参数里,这样就直接打开你指定的界面进行测试了.
@Test:
该注解用来定义一个测试用例,当你的测试类运行时,所执行的代码就是Test注解下的(Espresso还提供了其他的一些注解,比如:@After,@Before等,具体的用法可以去我上面写的android官网上查看),当然上面那段代码对应的就是sayHello测试方法,sayHello方法里所定义的就是要测试的内容,该内容的含义为:
onView(withText("Say hello!")).perform(click());
含义:根据文本”Say hello!”找个这个控件然后执行该控件的点击方法.
onView(withId(R.id.textView)).check(matches(withText("Hello, World!")));
含义:根据id “textView”找到这个控件然后检查该控件上面显示的文本是不是”Hello, World!”.
是不是很容易理解?非常语义化,一般都能看的懂.Espresso还提供了其他的方法供我们测试时调用,下面会列举一些常用的.
但是在列举常用的方法之前,需要先说明几点Espresso的注意事项,不然当你测试的时候会因为Espresso报的各种错误气个半死!!!
- 无论是通过withId()找控件还是通过withText()找控件或者其他方式比如withClassName(),withResourceName(),withTagKey()等方法,都要一定保证你所找的控件在当前页面确实存在且可见,不然会报:NoMatchingViewException,当然你可能还会碰到其他异常,比如AmbiguousViewMatcherException,AppNotIdleException异常等等,具体的报错原因可以到android官网查看,地址如下:Espresso的各种异常
- 如果你要测试AdapterView ,比如 ListView 或GridView等,使用上面的onView()方法是无效的,因为AdapterView的布局item是动态呈现的,没法直接指定,所以当你要测试AdapterView时,请把onView()方法换成onData() 方法,与onView()方法返回ViewInteraction类似,onData()方法返回DataInteraction,二者用法基本都是一样的.
perform()参数中常用的方法:
在上面的一段官网代码中,我们用到了perform(click()),那么除了click()方法还有其他功能强大的方法可以供我们使用,下面列举一些常用的方法:
- click():
返回一个点击action,Espresso利用这个方法执行一次点击操作,就和我们自己手动点击按钮一样,只不过Espresso把点击这个操作自动化了,下面的方法都是一样的道理,就不再赘述了. - clearText():
返回一个清除指定view中的文本action,在测试EditText时用的比较多 - swipeLeft():
返回一个从右往左滑动的action,这个在测试ViewPager时特别有用 - swipeRight():
返回一个从左往右滑动的action,这个在测试ViewPager时特别有用 - swipeDown():
返回一个从上往下滑动的action - swipeUp():
返回一个从下往上滑动的action - closeSoftKeyboard():
返回一个关闭输入键盘的action - pressBack():
返回一个点击手机上返回键的action - doubleClick():
返回一个双击action - longClick():
返回一个长按action - scrollTo():
返回一个移动action - replaceText():
返回一个替换文本action - openLinkWithText():
返回一个打开指定链接action
除了以上的常用方法还有其他一些不常用的,想继续研究的可以查看Espresso中的ViewActions类,需要注意的是,所有的方法包括上面说到的和没说到的,都有一个必须的前提条件,就是你要执行的view必须在当前界面上显示出来,这有两层意思:
1,当前界面必须能找到这个控件
2,这个控件必须是可见的
这是以上所有方法中通用的要求,当然有些方法还有额外的要求,比如必须要先获取焦点等
比如我们现在有这样一个场景:打开软件,滑动Viewpager的3个页面,在最后一个页面点击开始体验按钮进入主界面,点击预约叫车,由于没有登录所以会跳转到登陆页面,输入手机号,然后点击获取验证码按钮,然后输入验证码,最后点击登陆.
整个流程对应的代码测试代码如下:
@LargeTest
@RunWith(AndroidJUnit4.class)
public class StartActivityTest {
@Rule
public ActivityTestRule mActivityTestRule = new ActivityTestRule<>(StartActivity.class);
@Test
public void startActivityTest() {
//根据id找到ViewPager页面,并判断是否可见
ViewInteraction appCompatViewPager = onView(
allOf(withId(R.id.viewPager), isDisplayed()));
// 向左滑动viewpager页面,下面3句也可以写成一句话,Espresso会从左到右依次执行
// appCompatViewPager.perform(swipeLeft(),swipeLeft(),swipeLeft());
appCompatViewPager.perform(swipeLeft());
appCompatViewPager.perform(swipeLeft());
appCompatViewPager.perform(swipeLeft());
//根据文本找到"开始体验"按钮,并判断是否可见
ViewInteraction appCompatButton = onView(
allOf(withText("开始体验"), isDisplayed()));
//执行按钮的点击操作
appCompatButton.perform(click());
//根据控件的id和该控件的父布局id找到控件,并判断是否可见
ViewInteraction appCompatImageView = onView(
allOf(withId(R.id.appointmentCallCar), withParent(withId(R.id.callCarLayout)), isDisplayed()));
//执行该控件的点击操作
appCompatImageView.perform(click());
//根据id找到控件,并判断是否可见
ViewInteraction appCompatEditText = onView(
allOf(withId(R.id.phoneNumber), isDisplayed()));
//执行替换文本操作,说白了就是输入文本,输入完毕之后关闭输入法键盘
appCompatEditText.perform(replaceText("18894001263"), closeSoftKeyboard());
//根据id和显示的文本内容找到控件,并判断是否可见
ViewInteraction appCompatButton2 = onView(
allOf(withId(R.id.getPassword), withText("获取验证码"), isDisplayed()));
//执行该控件的点击操作
appCompatButton2.perform(click());
//根据id找到控件,并判断是否可见
ViewInteraction appCompatEditText2 = onView(
allOf(withId(R.id.password), isDisplayed()));
//执行替换文本操作,说白了就是输入文本,输入完毕之后关闭输入法键盘
appCompatEditText2.perform(replaceText("2454"), closeSoftKeyboard());
//根据id和显示的文本内容找到控件,并判断是否可见
ViewInteraction appCompatButton3 = onView(
allOf(withId(R.id.login), withText("登录"), isDisplayed()));
//执行该控件的点击操作
appCompatButton3.perform(click());
}
}
这里还需要说明一点,当所有的测试用例执行完毕之后,Espresso会自动关闭界面,根据动图也可以看到,当点击完登陆按钮之后,又回到了系统屏幕界面.
当测试完毕之后,在Android Studio的”Run”控制台可以看到测试结果,如下图:
如果测试顺利通过,会在下图中左侧显示All Test Passed:
如果测试没有通过,则会在右侧的控制台中输入错误信息,我们可以根据这些错误信息修改我们的代码然后再次进行测试.
以上的测试代码只是测试了启动和登陆功能,可以看到,套路都是一样的:根据id或文本等条件找到控件,然后执行控件的相关操作,这些代码都是重复的,唯一变化的就是执行的操作和查找的条件不一样,我们试想一下,如果项目一旦很大,我们一个一个手动的编写测试代码是不是很麻烦,并且都是一样的套路,没有一点技术含量.如果有种方式能自动生成这些测试代码,而我们只需要根据具体的测试情况修改甚至不改这些自动生成的代码就能完成测试,是不是就可以极大的节省我们的时间?而恰好Android Studio2.2版本提供了一个使用Espresso框架进行测试的图形化界面–Record Espresso Test功能,通过这个功能,我们只需要把软件运行到真机或模拟器上,然后就可以像平常手动测试软件一样,按照业务逻辑点击/滑动即可,Record Espresso Test功能会自动生成相应的测试代码,我们运行生成的测试代码,Espresso就可以自动的按照我们刚才操作的顺序自动的完成测试,是不是很方便啊.关于如何使用Record Espresso Test功能,以后会单独写一篇文章来说明,其实操作起来也很简单,大家可以先自己去尝试一下.
Hamcrest
上面出现了allOf这个东西,这其实是Hamcrest匹配器里的断言
JUnit4.4引入了Hamcrest框架,Hamcest提供了一套匹配符Matcher,这些匹配符更接近自然语言,可读性高,更加灵活。
Hamcrest 提供了大量被称为“匹配器”的方法。其中每个匹配器都设计用于执行特定的比较操作。Hamcrest的可扩展性很好,让你能够创建自定义的匹配器。最重要的是,JUnit也包含了Hamcrest的核心,提供了对Hamcrest的原生支持,可以直接使用Hamcrest。当然要使用功能齐备的Hamcrest,还是要引入对它的依赖。
Hamcrest匹配器
Hamcrest 提供了很强大的一些api 供我们进行测试断言。
操作类型 | 相关API | 备注 |
---|
- 核心
anything - 总是匹配,如果你不关心测试下的对象是什么是有用的
describedAs - 添加一个定制的失败表述装饰器
is - 改进可读性装饰器 - 见下 “Sugar” - 逻辑
allOf - 如果所有匹配器都匹配才匹配,像Java里的&&
anyOf - 如果任何匹配器匹配就匹配,像Java里的||
not - 如果包装的匹配器不匹配器时匹配,反之亦然 - 对象
equalTo - 测试对象相等使用Object.equals方法
hasToString - 测试Object.toString方法
instanceOf, isCompatibleType - 测试类型
notNullValue, nullValue - 测试null
sameInstance - 测试对象实例
Beans:
hasProperty - 测试JavaBeans属性 - 集合
array - 测试一个数组元素test an array’s elements against an array of matchers
hasEntry, hasKey, hasValue - 测试一个Map包含一个实体,键或者值
hasItem, hasItems - 测试一个集合包含一个元素
hasItemInArray - 测试一个数组包含一个元素 - 数字
closeTo - 测试浮点值接近给定的值
greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 测试次序 - 文本
equalToIgnoringCase - 测试字符串相等忽略大小写
equalToIgnoringWhiteSpace - 测试字符串忽略空白
containsString, endsWith, startsWith - 测试字符串匹配
这些API 几乎覆盖了我们测试断言的所有情况。再提供良好阅读性的情况下,减少了一些取值、循环、类型判断等代码的编写。