解放双手 - Android 开发应该尝试的 UI 自动化测试

本文由玉刚说写做平台提供写做赞助html

原做者:却把青梅嗅android

版权声明:本文版权归微信公众号玉刚说全部,未经许可,不得以任何形式转载git

困境

接下来我将说到这种状况并不是个例——做为一个Android开发者,当我实现了一个界面的一些功能,或者对界面上某些功能进行了修改,我该如何去查收我想要的结果呢?github

最简单的方式就是直接编译运行App,经过本身的操做对界面进行交互,从我的的视觉效果上进行功能的检查,好比我实现了一个RecyclerView,我就打开界面,看看这个列表是否正确显示在了界面上。缓存

不久以后,我以为某些地方代码不是很好,因而我改了一些代码,我怕会出现问题,因而为了保证项目可以不出问题(至少是避免低级的错误),我选择再次编译运行,验收结果。bash

再深刻一点,若是每一个版本发布前都须要这么屡次测试,或者每当咱们简单修改了一下代码,就须要更屡次重复进行以上步骤,并检测结果,来来每每,反反复复,实在使人乏味。微信

I choose to die

也许, UI自动化测试是一劳永逸解决这个问题的方案之一。网络

UI自动化测试简介

充满热情,一腔热血,说学就学,我行我上。架构

相信我,不要这样,这和学习或者框架不同,UI自动化测试是一个专业技能。不信的话,请参考一下各大机构对于测试工程师的培训周期,系统性走一遍全日制要几个月,闲暇时间学习?学不完的,并且,不必。app

Android官方文档的概述,AndroidStudio提供了几种UI测试工具供开发者使用。

事实上UI的自动化测试工具不少,但对于Android开发者来说,掌握其中的1至2项,就足以在UI测试领域立足,本文仅简单介绍基础的几款工具以抛砖引玉。

1. Monkey

Monkey是Android SDK自带的测试工具,在测试过程当中会向系统发送伪随机的用户事件流,如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试,也有日志输出。实际上该工具只能作程序作一些压力测试,因为测试事件和数据都是随机的,不能自定义,因此有很大的局限性。

2. Instrumentation

Instrumentation是早期Google提供的Android自动化测试工具类,虽然在那时候JUnit也能够对Android进行测试,可是Instrumentation容许你对应用程序作更为复杂的测试,甚至是框架层面的。经过Instrumentation你能够模拟按键按下、抬起、屏幕点击、滚动等事件。Instrumentation是经过将主程序和测试程序运行在同一个进程来实现这些功能,你能够把Instrumentation当作一个相似Activity或者Service而且不带界面的组件,在程序运行期间监控你的主程序。缺点是对测试人员来讲编写代码能力要求较高,须要对Android相关知识有必定了解,还须要配置AndroidManifest.xml文件,不能跨多个App。

3. UiAutomator

UiAutomator也是Android提供的自动化测试框架,基本上支持全部的Android事件操做,对比Instrumentation它不须要测试人员了解代码实现细节(能够用UiAutomatorviewer抓去App页面上的控件属性而不看源码)。基于Java,测试代码结构简单、编写容易、学习成本,一次编译,全部设备或模拟器都能运行测试,能跨App(好比:不少App有选择相册、打开相机拍照,这就是跨App测试)。缺点是只支持SDK 16(Android 4.1)及以上,不支持Hybird App、WebApp。

4. Espresso

Espresso是Google的开源自动化测试框架。相对于Robotium和UIAutomator,它的特色是规模更小、更简洁,API更加精确,编写测试代码简单,容易快速上手。由于是基于Instrumentation的,因此不能跨App。

以上这些工具的概述,节选引用自知乎:Android 手机自动化测试工具备哪几种?

如何入门?

UI的自动化测试的是一个复杂的系统,所谓望山跑死马,做为Android开发者,咱们想要经过闲暇的时间,指望短时间可以精通UI自动化测试是不现实的,可是每次都运行app手动测试又显得很蠢,最好的方式,是经过了解并学习一个经典的UI测试工具,在了解到UI自动化测试的好处以后,再选择继续深刻仍是功成身退

有心的同窗已经注意到了,上文中最后介绍的那个Espresso怎么这么眼熟呢?确实如此,在AndroidStudio2.2版本以后,在新建的项目中,AndroidStudio会默认添加Espresso的依赖。

这样看来,Espresso显然是一个不错的选择。正如Google所但愿的,当Android的开发者利用Espresso写完测试用例后,能一边看着测试用例自动执行,一边享受一杯香醇的Espresso(意式咖啡)。

Espresso学习指南

没事走两步

Google官方但愿咱们经过Espresso减小重复的劳动,那么这所谓的UI自动化测试效果如何呢,正所谓手下见真章,咱们来看一下Google的todo App的测试代码运行时的效果:

UI自动化测试效果

Espresso的原理是,经过测试代码模拟用户对UI元素的操做,以后再校验(verify)操做后的结果,和咱们人为操做不一样,Espresso可以在短期内测试全部的case,正如你所见的同样。

咱们不由这样想,若是一个界面涉及到不少的操做,没有Espresso测试代码以前,每次修改,工做的责任感须要让我本身先跑一遍全部功能,而后才敢打包扔给QA,可是若是我写好了自动化测试代码,是否是意味着每次改完代码,只需跑一遍测试代码就代替了以前的手工操做呢?

testEnd

如您所见,本次测试了一个界面19个不一样的操做,整个自动化过程共花费了4m34s,但在这个过程当中我能够冲一杯咖啡,或者看看技术博客,甚至是发呆——我惬意地获得了期冀的结果

若是我负责的功能模块全部界面,都覆盖了这样的测试代码,多好啊......——若是屏幕面前的您,有学习借助自动化测试工具偷懒的想法,请坚持阅读下去,以一个开发人员而非专业测试人员的视角,分享学习自动化测试的经验,这也正是本文的目的。

主旨

本文的目标是,以本身的学习经历为基础,为想要学习Espresso(或者有这个想法)的同窗,提供一个系统性的规划和建议

这意味着,本文不会去详细阐述每一个API的使用,于我而言,这些应该交给官方文档去阐释,固然,对于API而言,我也不认为可以讲述的比官方文档更优秀。

我会经过一些简单的测试代码阐述UI自动化测试所须要的一些基础或思想,可是代码自己不该该是本文的重点,我更但愿,当您读完本文,您能有啊,原来Espresso的UI测试应该这么学之感——而不是哦,原来这个API是这么用的

若是将本文的定义类比于该知识体系目录或者导航,我以为再恰当不过。

如何学习Espresso

个人建议是按照如下步骤进行学习:

1. Fork Google官方的Demo代码,运行并感觉测试代码的威力

Google官方的todo案例地址:

https://github.com/googlesamples/android-architecture

咱们拉下来代码后,选一个您比较感兴趣的分支,好比比较简单一点的todo-mvp分支,这个分支中代码的实现仅仅使用了MVP的架构,学习起来并不复杂。

咱们来看一下项目的目录结构:

其中androidTest和androidTestMock都是UI测试的代码,咱们先右键点击androidTest文件夹,run该文件夹下的全部UI测试case。

选中设备后,AndroidStudio就会编译并自动打包(注意实际上此处的测试打包和实际生产的打包并不同),而后自动在设备上运行全部的测试case——就和上文中的效果同样。

看到这里,咱们不只感叹测试代码的强大,不要沉迷于此,咱们继续第二步:

2. 阅读Espresso的官方文档

若是点进去看测试代码的话,咱们会比较懵逼,由于咱们对于Espresso的使用一无所知,那么接下来咱们要去作的,就是阅读Espresso的官方文档了:

Espresso官方文档:

https://developer.android.com/training/testing/espresso/basics

Espresso官方文档中文翻译:

https://lovexiaov.gitbooks.io/official-espresso-doc/content/

中文翻译的gitbook的确很差找,在此不只感叹UI自动化测试的小众性,特别感谢译者lovexiaov,没有你的分享精神,我就只能考虑本身去硬啃英文文档了。

实话说,中文的文档部分翻译不够准确,建议你们,有能力仍是看英文原版,我更建议你们中英文对照学习。

这一步,咱们不须要深刻学习并使用文档中列举的全部API,只须要参照文档看得懂todoApp中测试代码的用意就好了。

3. 付诸实践

当咱们参照API文档,而且可以基本看得懂demo代码中,大部分测试case想要干什么,咱们接下来就能够尝试付诸实践了。

实践

接下来我将会用简单的代码阐述Espresso的简单使用。

1.Hello Espresso!

来一个最简单的demo,当咱们点击一个Button,让界面某个TextView显示HelloEspresso的文字内容。

咱们忽略xml布局的实现,简单看一下Activity中的部分Java代码:

public void onViewClicked(View view) {
      switch (view.getId()) {
          case R.id.button:
              // 点击button后,textview显示hello espresso!
              textView.setVisibility(View.VISIBLE);
              textView.setText("hello espresso!");
              break;
      }
}
复制代码

很是简单,测试代码天然也浅显易懂:

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

    @Rule
    public ActivityTestRule<MainActivity> rule =
            new ActivityTestRule<>(MainActivity.class);

    @Test
    public void clickTest() {
        //检验:一开始,textView不显示
        onView(withId(R.id.textView))
                .check(matches(not(isDisplayed())));

        //检验:button的文字内容
        onView(withId(R.id.button))
                .check(matches(withText("修改内容")))
                .perform(click());  //操做:点击按钮

        //检验:textView内容是否修改,而且变为可见
        onView(withId(R.id.textView))
                .check(matches(withText("hello espresso!")))
                .check(matches(isDisplayed()));
    }
}
复制代码

代码很是简单,逻辑也很清晰,咱们测试的思路是,找到咱们要操做的界面元素,而后操做该界面元素,而后校验UI的变化。

在这个测试中,当咱们点击了button后,会校验:界面上TextView变为可见,同时附有“hello espresso!”的内容——若是测试失败了,说明咱们预期的操做并未获得预期的结果,咱们就须要去检查代码了。

2.模拟用户的登录操做

接下来咱们跳转另一个场景,稍微复杂一点,界面上有一个EditText,负责用户输入帐号,还有一个Button,负责登陆,还有一个TextView,当用户点击Button后,TextView会显示登陆成功而且清空输入框

public void onViewClicked(View view) {
      switch (view.getId()) {
          case R.id.button2:
            // 登录成功而且清空输入框
            textView.setVisibility(View.VISIBLE);
            textView.setText("登陆成功");
            editText.setText("");
            break;
      }
}
复制代码

咱们能够这样补充测试Case:

@Test
    public void loginTest() throws Exception {
        //先清除editText的内容,而后输入,而后关闭软键盘,最后校验内容
        //这里若是要输入中文,使用replaceText()方法代替typeText()
        onView(withId(R.id.editText))
                .perform(
                    clearText(),
                    replaceText("username"),
                    closeSoftKeyboard()
                )
                .check(matches(withText("username")));

        // 操做:点击Button
        onView(withId(R.id.button2))
                .perform(click());

        //校验:textView的内容和可见
        onView(withId(R.id.textView))
                .check(matches(withText("登陆成功")))
                .check(matches(isDisplayed()));

        //校验:editText的文字内容(被清空)和hint
        onView(withId(R.id.editText))
                .check(matches(withText("")))
                .check(matches(withHint("请输入帐户名")));
    }
复制代码

大功告成——和基本案例基本差很少,都是经过简单的对View的操做+校验完成了UI的测试代码编写。

看起来咱们已经熟悉了Espresso的使用套路,我已经有信心在真实的项目中应用它了。

3.熟练使用Espresso进行UI自动化测试

正所谓行百里路半九十,当咱们将看起来并不复杂的Espresso应用在真实的项目中时,咱们立刻就会遇到一个很严重的问题,那就是:

并不是全部的UI操做都是同步响应的!

Espresso进行一个简单的同步功能测试并不难,好比咱们点击了一个Button,点击后改变对应某个TextView的内容,这很简单。但实际正常开发中,这种简单的逻辑测试是不多见的,相反,咱们须要测试的是各类各样的异步测试,好比:

情景一:点击进入Activity,网络请求数据加载,成功后数据展现在界面上。 情景二:点击进入Activity,得到缓存,网络请求数据加载,成功后数据展现在界面上,处理缓存。 情景N : ......

假设这样一个简单的网络请求测试:

@Test
public void testHttp() {
   // 咱们请求网络数据,成功后让TextView显示"网络请求成功"
   // 同时ImageView从不可见变为可见

    //若是咱们直接检查是否是请求到了数据
    onView(withId(R.id.textView)).check(matches(withText("网络请求成功!")));
    onView(withText(R.id.imageView)).check(matches(isDisplayed()));
}
复制代码

若是咱们直接测试,那么很大几率会报错,由于在咱们要测试数据是否展现在UI上时,网络数据颇有可能尚未获取到。

这很难处理,由于咱们不知道数据到底何时才能获取到,有同窗抖了个机灵,说咱们能够这样:

@Test
public void testHttp() {
    // 咱们一进来就先让他等待5秒,等数据加载完毕再检查UI
    Thread.sleep(5000);

    // 5秒结束,咱们检查是否是请求到了数据
    onView(withId(R.id.textView)).check(matches(withText("网络请求成功!")));
    onView(withText(R.id.imageView)).check(matches(isDisplayed()));
}
复制代码

这样能够实现吗,这个大几率真的能够,可是这种测试显然问题不少,由于网络状况是在不断变化的,也许0.5s就能获取网络数据,也有可能数十秒后才能获取,这样前者致使咱们浪费了4.5s的时间,后者在网络状态属于正常的时候测试结果失败,这都是咱们不肯看到的结果。

咱们更但愿在获取到网络数据以后,当即进行下一步的测试,所以咱们须要对网络数据的获取状况进行监听。

可是问题来了,如何在UI测试代码中,对真实的网络状态进行监听呢?

这个问题难倒了我,好在Google的工程师们已经在todo的demo中提供了一种解决的方式,咱们来看一看官方的方案。

4.异步操做的测试思路

Google官方提供了IdlingResource以供开发者进行UI的异步测试,对于IdlingResource的解释说明,咱们能够参照官方文档:

https://developer.android.com/training/testing/espresso/idling-resource

或者中文文档对于IdlingResource的解释:

https://lovexiaov.gitbooks.io/official-espresso-doc/content/chapter6.html

我不会用大段代码阐述如何使用,它的基本原理是:在生产代码中,定义一个Flag(标记),当开始异步请求前,修改Flag的状态,当网络请求结束后,将Flag的状态重置,这时候Flag状态修改的事件会被发送通知给注册的对象(好比测试代码):

@Before
public void setUp() throws Exception {
    idlingresource = activityRule.getActivity().getIdlingresource();
}

@Test
public void onLoadingFinished() throws Exception {
    // 再也不须要这样的代码
    // Thread.sleep(5000);

    // 注册异步监听,此时测试会被挂起
    // 当网络请求结束后,生产代码中Flag状态的改变,会继续执行测试代码
    Espresso.registerIdlingResources(idlingresource);

    // 继续执行代码
    onView(withId(R.id.text))
            .check(matches(withText("success!")));
}

@After
public void release() throws Exception {
    // 固然,咱们须要在测试结束后取消注册,释放资源
    Espresso.unregisterIdlingResources(idlingresource);
}
复制代码

这种行为的好处不言而喻,它可以在异步结束以后立刻执行接下来的测试代码,从效果上来讲,相比Thread.sleep(5000);不知好了多少。

它的缺点也很明显,那就是测试代码实际须要依赖对生产代码进行配置(本文中并未展现,请参考todoDemo或者这篇文章)。

难道就没有更好的解决方案吗?固然有,Google对此的建议是,重构项目代码(好比增长product flavors,或者经过依赖注入等等),使其变得可测试性——到这一步,请慎重考虑,由于这已经涉及到项目的架构以及项目管理的层级上了。

5.更多

实际上,Espresso在应用在实际项目中,须要咱们去面对的问题并很多,绝大多数状况下,这些问题都可以经过搜索引擎而后亲自实践去解决—— 你毫不是一我的在战斗。

小结——坚持仍是放弃?

不少同窗都了解单元测试UI自动化测试的重要性,可是这些工具须要不菲的时间成本,那么它们真的还有必要去学习吗?

有位同窗举手了,他一样表示——有时虽然修改一个小功能,须要开发者屡次手动测试很麻烦,可是也并不是不可接受,至少上班时,在项目编译运行期间,我能够切换网页,看看新闻摸摸鱼

固然还有这样的状况,做为一个开发人员,即便我学会了自动化测试,我也不必定有机会去应用它,直接尝试应用在一个已经完善的成熟项目中,是不现实的;这样的话,会不会出现,学会了但根本用不到的窘境?

每一个人都不能保证未来仍是否还会遇到曾经的问题,若是遇到了,我该怎么作,是选择继续躲避,仍是一劳永逸;并且,即便学会了,如何保证这能成为我核心竞争力的一部分,而不是学会了却用不到,最终被慢慢忘记?

的确,咱们有时的确没有必要为工做作出额外的付出(思考和实践),万一搞砸了,反而不如不作。可是我要阐述的一点是:不作并不意味着问题被解决了——你只是暂时避开了它,而下一次遇到它的时候,你仍需去面对这个困境;而且,若是将测试任务交给了代码,摸鱼的时候岂不更加轻松?正所谓,授人以渔,劳神费力。 而——

个人思路

言归正传,对于如何实践自动化测试,个人方式是对我的的一些工具代码进行UI自动化测试的覆盖,在进一步完善本身的工具同时,深刻了解Espresso。

笔者对于Espresso的经验所得来自于本身的**Github这个工具,它是Android的一个响应式图片选择器**,所以每次发布新版本笔者都须要本身测试UI,而UI自动化测试无疑能够减小这些重复的操做。 ——这个库UI测试的更详细过程并不是本文的重点,我在另外一篇文章中去阐述了它:

全副武装!AndroidUI自动化测试在RxImagePicker中的实践历程

一千个观众眼中有一千个哈姆雷特,只要感兴趣,总能找到适合本身的方式,本文所讲述的Espresso仅仅是UI自动化测试这门专业技术的一部分,但我认为它很契合Android开发者,并借助它为本身的UI界面进行白盒测试(也有朋友称Espresso为灰盒测试),正如官方文档所描述的(下为译文):

Espresso 的使用群体为坚信自动化测试是开发周期中必不可少的一部分的开发者。虽然它可被用来作黑盒测试,但 Espresso 会在对被测代码库熟悉的人手中火力全开

在咱们感叹AndroidStudio默认提供的依赖库中,JUnit4可让咱们经过单元测试保证最小模块代码的可靠性,ConstraintLayout让咱们减小大量布局嵌套的同时慢慢抛弃了RelativeLayout的同时,请也不要忽视Espresso,真正了解了它并付诸实践,便会对它强大的UI自动化测试功能爱不释手。

欢迎关注个人微信公众号「玉刚说」,接收第一手技术干货
相关文章
相关标签/搜索