Android 自动化测试 Espresso篇:简介&基础使用


目录(?)
[+]

前言

最近在尝试深入学习Android单元测试。

以前笔者对於单元测试的理解很简单,做一个简单的API测试获取Server端数据,或者测试对应简单的工具类和数据处理的逻辑。

经过这近一个月的碎片化学习,深深了解到了单元测试的重要性,想找个机会总结一下,但是网上很多前辈都已经做出了更精辟的总结:

@小创作:为什么要做单元测试

更多理由不系表,如果有机会,笔者会专门写一篇关于对於单元测试浅陋的理解以及单元测试在开发中的重要性。个人认为项目中添加单元测试的优势有:

  • 方便重构

  • 节约时间

  • 提升代码设计

在AndroidStudio2.2+的版本,新建的Project中会自动为开发者添加Espresso自动化测试库的依赖:

这里写图片描述

这说明谷歌官方也十分推崇开发者在开发时通过Espresso进行自动化测试,从而提高开发效率,我们作为Android开发者,也有必要去抽时间去了解一下其优势.

顺便提一句,AndroidStudio2.3+的版本中,细心的同学可以发现,新建的Project中会为开发者添加ConstraintLayout(约束布局)的依赖,该布局大幅提升了xml文件UI绘制的速度,并且避免了多层布局嵌套的苦手。有兴趣的同学可以去了解一下。

笔者的Android单元测试相关系列:

Android单元测试:Mockito使用详解

Android单元测试:使用本地数据测试Retrofit

Android单元测试:测试RxJava的同步及异步操作

Android 自动化测试 Espresso篇:简介&基础使用

Android 自动化测试 Espresso篇:异步代码测试

一.简介,我们为什么使用Espresso

Espresso 是在2013年的 GTAC 上首次提出,目的是让开发人员能够快速地写出简洁,美观,可靠的 Android UI 测试。

举个简单的例子,我们这边有一个功能,点击界面Button,网络请求加载图片,正常开发人员想测试,需要:

点击run -> gradle build -> install apk -> 手动点击按钮 ->本人监测结果

一般的方式

其实并不麻烦,但是考虑一下,如果每个版本发布前都需要这么多次测试,或者我们简单修改了一下代码,那么我们需要再次进行以上步骤,并监测结果,来来往往,反反复复,实在是子子孙孙无穷匮也!

如果使用Espresso,我们只需要运行代码,代码会自动帮助我们执行这些流程(点击按钮)并且监测结果(是否加载到对应图片):

这里写图片描述

可以看到上面实际上,安装,进入界面,点击按钮,加载图片,判断结果,都是自动化的测试,不需要开发者去人为监测。

其实相比较於单元测试,Espresso我个人理解更倾向于像自动化集成测试,因为这个库实质上是开发者进行测试的时候,在测试设备上面安装了两个apk(开发者一般的apk和AndroidTest apk),然后模拟操作(比如点击按钮,滑动列表等等)在界面上,将开发者预期的结果和实际的结果进行对比。

二.Espresso测试 对比 单元测试(UnitTest)

这和单元测试(UnitTest)实际上是有本质的区别的,首先我们并不推荐单元测试中有真正的网络数据交互,通常我们的方式是本地mock一个模拟数据,然后进行测试。

其次,单元测试的功能一定是要单一的,并且是最基础的一个单元,测试的功能越单一,耦合度越低越好。这样如果我们需要测试一个复杂的功能,只需要把数个单一功能的模块分别测试,只要这些小模块都没有问题,组合出来的复杂功能自然也就不会出现什么问题了。

但是Espresso最强大的功能是UI界面的自动化测试,不可否认它支持异步请求(网络请求,数据库操作),但是成本过于高昂(需要大量的代码,而且需要实现idlingResource接口,这个后文再提)。

同时,Espresso需要依赖Android设备.这将导致我们将花费更多时间在编译apk和AndroidTest apk的安装上(即使已经比一般的手动测试快了很多倍)。

最后,它的耦合度看起来确实很大,正常我们项目的需求不可避免的都需要网络请求,那么我们简单的测试点击按钮,实际上还测试到了网络请求,图片加载等等,这样一个测试用例,实际上耦合了很多的功能,这不符合单元测试的思想。

但是Espresso一无是处了吗?并非如此。

转机

我们前文提到了,Espresso最强大的功能就是UI自动化测试,这是其他单元测试框架达不到的,所以我们完全可以通过Espresso进行界面数据展示的功能测试。

简而言之,我们可以让Espresso处理它拿手的UI界面测试,而网络请求等业务处理,我们可以交给其他测试框架去处理,比如Mockito(后文再讲)。

这里我们就要提到经典的MVP设计模式,MVP模式将数据的展示处理交给了View,业务代码交给了Model,我们完全可以通过MVP模式,将测试代码分开来测试,这样我们的问题就解决了。

说了这么多,不要好高骛远,笔者的意思是Espresso是值得Android开发人员花费时间成本去学习的,接下来做一个简单的案例,了解一下Espresso的简单使用。

三.Hello Espresso!

我们先来看一下我们的需求:

这里写图片描述

很简单,可以分为两个功能:

1,点击登录按钮,显示登陆成功Success,同时清空输入框 
2,点击修改内容,显示hello espresso!

1.基础代码

看一下基本代码,很简单:

xml,布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".a_espresso.a01_simple.A01SimpleActivity">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <Button
            android:id="@+id/btn01"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="修改内容" />

        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone" />

        <EditText
            android:id="@+id/et_01"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="请输入账户名"
            android:text="123456"
            />

        <Button
            android:id="@+id/btn02"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="登录" />

    </LinearLayout>

</android.support.constraint.ConstraintLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

Activity中对点击事件的处理:


    @OnClick({R.id.btn01, R.id.btn02})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn01:
                //显示hello espresso!
                tvContent.setVisibility(View.VISIBLE);
                tvContent.setText("hello espresso!");
                break;
            case R.id.btn02:
                //登陆成功并且清空输入框
                tvContent.setVisibility(View.VISIBLE);
                tvContent.setText("success");
                et01.setText("");
                break;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.测试代码

在你的AndroidTest目录下创建一个测试类,名字无所谓,但是为了规范我起名为Activity全名+Test:

比如MainActivity -> MainActivityTest

这样一目了然,见名知意。

这里写图片描述

测试类代码如下,有注解相信并不难理解:

@RunWith(AndroidJUnit4.class)
public class A01SimpleActivityTest {

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

    @Test
    public void clickTest() {
        //tvContent是否默认不显示
        onView(ViewMatchers.withId(R.id.tv_content))
                .check(matches(not(isDisplayed())));    //是否不可见

        //检查btn01的text,然后执行点击事件
        onView(withId(R.id.btn01))
                .check(matches(withText("修改内容")))
                .perform(click());

        //检查tv内容是否修改,并且是否可见
        onView(withId(R.id.tv_content))
                .check(matches(withText("hello espresso!")))
                .check(matches(isDisplayed()));
    }

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

        //点击登录
        onView(withId(R.id.btn02))
                .perform(click());

        //校验内容
        onView(withId(R.id.tv_content))
                .check(matches(withText("success")))
                .check(matches(isDisplayed()));

        onView(withId(R.id.et_01))
                .check(matches(withText("")))           //内容是否为""
                .check(matches(withHint("请输入账户名")))         //hint内容是否为"请输入账户名"
                .check(matches(withHint(containsString("账户名"))));       //hint内容是否包含"账户名"

        Thread.sleep(3000);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

需要注意的是,因为我们实际上有两个功能要测试,所以笔者建议最好每个功能分开写一个Test方法。

比如该测试类中,clickTest()测试点击显示Espresso,loginTest()测试登陆功能。

当然所有功能写一起也可以,但是这样本身就不太符合我们对测试的定义,多写几个测试方法,保证每个方法只涉及一个功能。这样才符合单元测试的粒度。

我们随便选择一个功能,以loginTest为例,运行测试代码(录屏有所瑕疵,见谅!):

这里写图片描述

查看结果:

这里写图片描述

测试通过。

总结

本文介绍了Espresso的实用性,同时通过了一个简单的入门案例,了解了Espresso的基本使用方式,如果对于Espresso某个方法不理解,还请查阅相关API文档:

Espressp 谷歌官方使用文档

建议不熟悉的同学上手一遍,基本就能了解了,本身Espresso的方法可以称得上是见文知意了。

本系列的所有代码都已托管GitHub:

GitHub代码托管地址,点我!点我!点我!

本文案例代码:

这里写图片描述

下一篇文章,我们将对Espresso的异步网络请求进行简单的学习,欢迎关注。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章