<b>本文讲的是[译] 使用 Espresso 和 Mockito 测试 MVP,</b>
作为软件开发者,我们尽最大努力做正确的事情确保我们并非无能,并且让其他同事以及领导信任我们所写的代码。我们遵守最好的编程习惯、使用好的架构模式,但是有时发现要确切的测试我们所写的代码很难。
就个人而言,我发现一些开源项目的开发者非常善于打造令人惊叹的产品(可以打造任何你可以想象的应用),但是由于某些原因缺乏编写正确测试的能力,甚至一点都没有。
本文是关于如何对广泛应用的 MVP 架构模型进行单元测试的简单教程。
那么这个应用究竟长什么样呢?
这是一个非常简单的 Android 应用,它只做一件事:当点击按钮时隐藏或者显示一个 TextView。
这是应用起初的样子:
Initial
这是按钮点击后的样子:
724E8fE.png
出于文章的需要,我们假设这是一个价值数百万的产品,并且它现在的样子将会持续很长时间。一旦发生变化,我们需要立刻知晓。
应用中有三部分内容:一个有应用名的蓝色工具栏,一个显示 “Hello World” 的 TextView,以及一个控制 TextView 显隐的按钮。
我们开始吧!
我们首先对炫酷的 ToolBar 进行测试。毕竟是一个价值数百万的应用,我们需要确保它的正确性。
如下是测试 ToolBar 的完整代码。如果你看不懂这到底是什么鬼,也没关系,后面我们一起过一下。
首先,我们需要告诉 JUnit 所执行测试的类型。对应于第一行代码(@runwith (AndroidJUnit4.class))。它这样声明,“嘿,听着,我将在真机上使用 JUnit4 进行 Android 测试”。
测试代码存放在 androidTest 目录。
android_test_directory
下面我们看一下 “ActivityTestRule”,如下 Android 文档做出了详细的介绍:
本质上是说,“这是我要测试的 Activity”。
下面我们具体看下 testToolBarDesign() 方法具体做了什么。
这段测试代码是找到 ID 为 “R.id.toolbar” 的 view,然后检查它的可见性。如果本行代码执行失败,测试会立刻结束并不会进行其余的测试。
这行是说,“嘿,让我们看看是否有文本内容为 R.string.app_name 的 textView ,并且看看它的父 View 的 id 是否为 R.id.toolbar”。
最后一行的测试更有趣一些。它是要确认 toolbar 的背景色是否和应用的首要颜色一致。
在讲代码之前,我需要说下代码有点长,但是十分易读。我对代码内容作了详细注释。
这段代码主要功能是保证应用打开时,ID 为 “R.id.tv_to_show_hide” 的 TextView 处于显示状态,并且其显示内容为 “Hello World!”
然后检查按钮也是显示状态,并且其文案(默认)显示为 “Hide”。
接着点击按钮。点击按钮十分简单,如何实现的也十分易懂。这里我们对找到相应 ID 的 view 执行 .perform() (而非 “.check”),并且在其内执行 click() 方法。perform() 方法实际是执行传入的操作。这里对应是 click() 操作。
因为点击了 “Hide” 按钮,我们需要验证 TextView 是否真的隐藏了。具体做法是在 disDisplayed() 方法前置一个 “not()”,并且按钮文案变为 “Show”。其实这就和 java 中的 “!=” 操作符一样。
后面的代码是前面代码的反转。再次点击按钮,验证 TextView 重新显示,并且按钮文案符合当前状态。
就这些。
如下是全部的 UI 测试代码:
单元测试最大特点是在本机的 JVM 环境上运行(与 Android 测试不同)。无需连接设备,测试跑的也更快。缺点就是无法访问 Android 框架 API。总之进行 UI 之外的测试时,尽量使用单元测试而非 Android/Instrumentation 测试。测试运行的越快越好。
下面我们看下单元测试的目录。单元测试的位置与 Android 测试不同。
different_location
开始前我们先看下 presenter 以及关于 model 需要考虑的问题。
很简单。两个方法:一个检查 view 是否可见。如果可见就隐藏它,反之显示。之后将按钮的文案改为 “Hide” 或 “Show”。
reverseViewVisibility() 方法调用 “model” 对传入的 view 进行可见性设置。
两个方法:showView(View) 和 hideView(View)。具体功能十分直观。检查 view 是否为 null,不为 null 则对其进行显隐设置。
现在我们对 presenter 和 model 都有所了解了,下面我们开始测试。毕竟这是一个数百万的产品,我们不能有任何错误。
我们首先测试 presenter。当使用 presenter (任何 presenter)时,我们需要确保 view 已与之关联。注意:我们并不测试 view。我们只需要确保 view 的绑定以便确认是否在正确的时间调用了正确的 view 方法。记住,这很重要。
这里我们使用 Mockito 进行测试,就像单元测试那样,我们需要告诉 Android,“嘿,我们需要使用 MockitoJUnitRunner 进行测试。”实际操作时在测试类的顶部添加 @RunWith (MockitoJUnitRunner.class) 即可。
从前面可知我们需要两个东西:一是模拟一个 View (因为 presenter 使用了 View 对象,对其进行显隐控制),另外一个是 presenter。
下面展示了如何使用 Mockito 进行模拟
我们要写的第一个测试是 “testReverseViewVisibilityFromVisibleToGone”。顾名思义,我们将要验证的是,当可见的 View 被传入 presenter 的 reverseViewVisibility() 方法时,presenter 能正确地设置 View 的可见性。
我们一起看下,这里具体做了什么?由于我们要测试的是 view 从可见到不可见的操作,我们需要 view 一开始是可见的,因此我们希望一开始调用 view 的 isShown() 方法返回是 true。接着,以模拟的 view 作为入参调用 presenter 的 reverseViewVisibility() 方法。现在我们需要确认 view 最近被调用的方法是 setVisibility(),并且设置为 GONE。然后,我们需要确认与 presenter 绑定的 view 的 setButtonText() 方法是否调用。并不难吧?
嗯,接着我们进行相反的测试。在继续阅读下面的代码之前,试着自己想一下怎么做。如何测试从隐藏到显示的情况?根据上面已知的信息思考一下。
代码实现如下:
接着测试 “Model”。和前面一样,我们首先在类顶部添加注解 @RunWith (MockitoJUnitRunner.class) 。
如前面所说,Utils 类首先检查 view 是否为 null。如果不为 null 将执行显隐操作,反之什么都不会做。
Utils 类的测试十分简单,因此我不再逐行解释,大家直接看代码即可。
我解释下 testShowViewWithNullView() 和 testHideViewWithNullView() 方法的作用。为什么要进行这些测试?试想下,我们不希望因为 view 为 null 时调用方法造成整个应用的崩溃。
我们看下 Utils 的 showView() 方法。如果不做 null 检查,当 view 为 null 时应用会抛出 NullPointerException 并崩溃。
另外一些情况下,我们需要应用抛出一个异常。我们如何测试一个异常?十分简单:只需要对 @Test 注解传递一个 expected 参数进行指定:
如果没有异常抛出,该测试会失败。
本文接近尾声,需要提醒大家的是:测试并不总是像本例这样简单,但也不意味着不会如此或不该如此。作为开发者,我们需要确保应用正确的运行。我们需要确保大家信任我们的代码。我已经持续这样做许多年了,你可能无法想象测试拯救了我多少次,甚至是像改变 view ID 这样最简单的事。
没有人是完美的,但是测试让我们趋近完美。保持编码,保持测试,直到永远!
<b></b>
<b>原文发布时间为:2017年5月24日</b>
<b>本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。</b>