在 MVP 中进行单元测试

对于测试,大家都不陌生,但是我相信还是有部分开发觉得测试工作和自己没有直接关系、测试工作是测试工程师的事。惭愧的说,本人也是很长一段时间内没真正理解“测试”这件事儿,之前呆过的几家公司都没有真正的“测试工程师”,确切的说,是没有会写代码的测试工程师,基本上都是手动测试,然后输出报告,测试无需懂技术,我相信国内很多公司都是这样,特别是 App 端的测试,很少有白盒测试的。这篇要说的东西不多,主要来说说单元测试,由于本人也是最近才开始实践,文章抛砖引玉,如果有说得不到位的地方,希望读者给予指正。

0. 提纲

  • 0.1 什么是单元测试
  • 0.2 单元测试的意义
  • 0.3 Android 开发中进行单元测试
  • 0.3.1 AndroidStudio 中配置
  • 0.4 开源框架
  • 0.5 MVP 架构中如何写单元测试
  • 0.6 例子

1. 什么是单元测试

我们先来做这么一个体验。假设现在我们有个开发者要完成某个功能的开发,现在我们从电脑屏幕的视角来观察他接下来的一序列行为:

   他创建了一个类:A。
    光标在同一个地方闪烁了好久,他应该在思考 ....
    他在 A 写了一个方法 a。
    接着写了方法b、c、d。
    写的有点多了,滚动条来回滑动,他在阅读代码梳理逻辑。
    他打开了启动器,运行了A。
    运行报错了。
    他继续修改方法。
    再次打开启动器,运行了A。
    运行成功了,他收获了满足感。
    ......

在这个观察中,我们看到他每次启动运行 A 所做的事其实就是“测试”,所以单元测试这件事其实是每个开发都做得最多的一件事,只是怎么让这件事变得更高效、更有意义是接下来要讲的。最后给出一个官方性的定义:单元测试又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。 程序单元是应用的最小可测试部件。

2. 单元测试的意义

我们先来看看一张图:

这张图叫做测试金字塔,单元测试处于开发环节比较早期,粒度也比较小,也是最重要的一个环节,对整体测试的效率至关重要,这部分做好了,后期的测试才会更有效,试想如果整栋楼建好了才发现砖头质量有问题,那是多么糟糕的事;单元测试就像对每个螺丝钉测试一样,生产螺丝钉的时候发现螺丝钉有问题很容易解决,如果在机器运转故障的时候去定位是不是螺丝钉、具体是那颗螺丝钉就难了。单元测试的意义可以总结如下几点:

  • 代码质量的基础保障。
  • 单元测试,模块区间比较小,更容易发现问题,发现问题更容易定位。
  • 为后期集成测试节省了很多时间。
  • 单元测试是对程序结构的一个观察者视角审视,有利于我们提高代码的整体设计能力。

3. Android 开发中进行单元测试

3.1 AndroidStudio 中配置

Android 为我们提供了两种跑测试的方式:

  • Unit Tests
  • Android Instrumentaion Tests

3.1.1 Unit Tests

Unit Tests 这种方式,跑的测试代码运行在本机 JVM 上,不需要编译Apk ,不需要 Android 设备的支持,速度相对快,当然,测试的对象不能含有 Android 的 API,否则运行时会报错。这个模式下,测试代码的默认路径是 test/java。单元测试的基础框架是 Junit,需要配置依赖:

    testCompile "junit:junit:4.12"

然后我们来写一个被测试类:Calculator.java

public class Calculator {
        public  int add(int a, int b) {
            return a - b;
        }
}

把Test Artifact 切换到 Unit Tests 模式,然后我们创建一个针对这个类的测试:CalculatorTest.java

选择 create new test

public class CalculatorTest {

    @Before
    public void setUp() throws Exception {

    }
    @After
    public void tearDown() throws Exception {

    }
    @Test
    public void testAdd() throws Exception {

    }

}

自动生成了选择的测试方法,@Before、 @After、@Test 这几个注解来自 Junit 的 API,@Before 标记的方法,在测试用例启动时最先执行,然后执行 @Test 标记的方法,最后执行 @After 标记的方法。一般会在 @Before 的方法中做初始化工作,例如:创建被测试的对象, @Test 标记的就是我们要测试的方法,在其中执行验证方法。完善我们的测试代码:

public class CalculatorTest {

    private Calculator calculator;
    @Before
    public void setUp() throws Exception {
               calculator = new Calculator();
    }
    @After
    public void tearDown() throws Exception {

    }
    @Test
    public void testAdd() throws Exception {
                // 验证方法执行结果,1+1 是否等于 2
       assertEquals(2, calculator.add(1 , 1));
    }

}

然后在这个类上右键,我们就可以运行这个测试类了。

3.1.1 Android Instrumentaion Tests

Android Instrumentaion Tests 包的测试代码运行在 Android 设备上,速度相对慢一些。测试代码默认路径是 androidTest/java。为了让测试能跑在设备上,需要 AndroidJUnitRunner 的支持:

defaultConfig {
    applicationId "com.dalimao.demoforunittest"
    minSdkVersion 15
    targetSdkVersion 23
    versionCode 1
    versionName "1.0"
            // support Instrumentaion Tests
    testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
 }

配置依赖:

androidTestCompile "com.android.support.test:runner:0.4.1"

同时也离不开 Junit 的支持,配置依赖:

 androidTestCompile "junit:junit:4.12"

创建测试和 Unit Tests 的一样,运行的时候需要连接模拟器或手机。

4 开源框架

测试框架很多,这里只简单说说比较流行的组合以及本例子中要使用的框架:

  • Junit 是最简单最基本的 Java 单元测试框架。
  • Mockito 强大的 Java 测试框架,强大之处在于能很方便的模拟对象,改变对象行为,同时提供了很多验证API。
  • espresso:google 提供的 U I测试框架。

5. MVP 架构中如何写单元测试

理解了 AndroidStudio 中如何写最基本的单元测试,再结合几个开源框架,我们可以来写一些更为贴近实际开发的测试用例了。 目前 MVP 是 Android 项目的比较流行的架构模式,它的优点在代码的易测性
上也得到了极好的体现,对 MVP 不熟悉的考验参考我之前的几篇博客:

对于 MVP 个层次我们怎么来写测试代码? 先来看看框架的选型:

View层

View的测试自然依赖于Android环境,同时需要模拟U交互的能力,使用 Espresso + JUnit,Espresso用于模拟和验证各种各样的UI操作,使用 Android Instrumentaion Tests 运行,代码存放于AndroidTest中。

Presenter层

Presenter 上对UI,下对 Model 都是接口抽象,控制流程逻辑,不涉及 Android API,用 Junit + Mockito 测试即可,使用 Unit Tests 运行,代码存放于test中。

Model层

负责数据的存取,数据可能来自于网络、数据库和内存,如果涉及设备相关的API、依赖Android系统环境,则使用 Android Instrumentaion Tests 运行,代码存放于androidTest中;反之可以Junit+Mockito,使用 Unit Tests 运行,代码存放于test中。

6. 例子

## 6.1 场景介绍
这是一个简单的场景,一个游戏列表页面。对于列表页面,通常都会有缓存逻辑,加载数据时会优先去加载本地数据,如果没有,则加载远端数据。本例使用 MVP 来架构这个简单的场景(引入了 EventBus 来代替常规的异步回调,EventBus 极其简单,不过不理解也不影响对本章主题的理解),然后真对 M 和 P 分别写了对于的单元测试,本例没有写 View 层的单元测试(个人觉得对 View 写单元测试的必要性不大,如果你的感受相反,非常感谢你给我一个说服我的理由),我们先来流程图:

至此,建议先你下载 demo 源代码,再一起完成下面的理解。认真的人不会找不到源码地地址

6.2 目录结构

  • test/java :该目录下写了针对 GamePresenter 的单元测试 GamePresenterTest 和针对 GameManager 的测试类 GameManagerTest (这个跑不过,因为涉及类 Android 的 API)
  • androidTest / java: 该目录下写 针对 GameManager 的测试类 GameManagerAndroidTest (逻辑和 GameManagerTest 一样)

6.3 例子中涉及 Mockito 的注解和说明

@Mock :对成员变量的注解,被这个注解的变量不用手动实例化,Mockito 会帮你注入一个模拟的对象(需要在 @Before 标记的方法中先执行 MockitoAnnotations.initMocks(this)。例如:

   @Mock
 private IGameManager gameManager;

初始化 MockitoAnnotations

public void setUp() throws Exception {
    //Log 是 Android 的 API,Unit test 模式下关闭使用
    LogUtil.setDebug(false);
    // 初始化 @Mock 注解功能,自动注入 @Mock 标记的对象
    MockitoAnnotations.initMocks(this);
    //gameManager 已经实例化

}

verify(T):这是 Mockito API 提供的一个静态方法,用来验证某个 mock 对象(@Mock 标记的对象)是否执行了某个方法,执行了多少次。verify(T),返回是一个泛型,你传入什么类型他就返回什么类型的子类给你,重写的方法中验证传入对象对应的方法是否执行过。例如:

//验证 gameListView.showLoading() 是否执行
verify(gameListView).showLoading();

when(Object.method()).thenReturn(ReturnObject): 这是 Mockito API 提供的一个静态方法,用来改变方法的对象的行为(方法),当 Object.method() 调用的时候返回 ReturnObject ,用来模拟方法的返回值。例如本例中模拟获取本地数据为空:

   //模拟本地数据为空
    when(localGameDataSource.getData()).thenReturn(null);

认真的人不会找不到源码地地址,欢迎留言。

时间: 2024-12-03 22:39:35

在 MVP 中进行单元测试的相关文章

[Android]Android MVP&依赖注入&单元测试

以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5422443.html Android MVP&依赖注入&单元测试 注意:为了区分MVP中的View与Android中控件的View,以下MVP中的View使用Viewer来表示. 这里暂时先只讨论 Viewer 和 Presenter,Model暂时不去涉及. 1.1 MVP 基础框架 1.1.1 前提 首先需要解决以下问题: MVP中把Layout布局

在Python的Flask框架中实现单元测试的教程

  在Python的Flask框架中实现单元测试的教程,属于自动化部署的方面,可以给debug工作带来诸多便利,需要的朋友可以参考下 概要 在前面的章节里我们专注于在我们的小应用程序上一步步的添加功能上.到现在为止我们有了一个带有数据库的应用程序,可以注册用户,记录用户登陆退出日志以及查看修改配置文件. 在本节中,我们不为应用程序添加任何新功能,相反,我们要寻找一种方法来增加我们已写代码的稳定性,我们还将创建一个测试框架来帮助我们防止将来程序中出现的失败和回滚. 让我们来找bug 在上一章的结尾

iOS开发中的单元测试(二) 让断言活泼起来的匹配引擎

上一篇文章简单介绍了OCUnit和GHUnit两款iOS开发中较为常见的单元测试框架,本文进一步介绍单元测试 中的另一利器--匹配引擎(Matcher Engine).匹配引擎可以替代断言方法,配合单元测试引擎使用,测试 用例可以更多样化,更细致. 传统断言提供的方法数量和功能都有限,以导读中提到的两款框架为例 ,即使是断言相对丰富的GHUnit也只是提供了38种断言方法,范围仅涵盖了逻辑比较,异常和出错等少数几方 面,仍然很单一.而使用匹配引擎代替断言,可能性就大大丰富了,除了普通断言支持的规

在JavaScript的AngularJS库中进行单元测试的方法

这篇文章主要介绍了在JavaScript的AngularJS库中进行单元测试的方法,主要针对AngularJS中的控制器相关,需要的朋友可以参考下 开发者们都一致认为单元测试在开发项目中十分有好处.它们帮助你保证代码的质量,从而确保更稳定的研发,即使需要重构时也更有信心. 测试驱动开发流程图 AngularJS的代码声称其较高的可测性确实是合理的.单单文档中列出端对端的测试实例就能说明.就像AngularJS这样的项目虽然都说单元测试很简单但真正做好却不容易.即使官方文档中以提供了详尽的实例,但

在Android中进行单元测试遇到的问题

问题1.Cannot connect to VM  socket closed 在使用JUnit进行测试的时候,遇到这个问题.网上的解释是:使用Eclipse对Java代码进行调试,无论是远程JVM还是本地JVM都会进行Socket通讯.发生这样的错误是由于这些软件会修改winsock,还会监听和占用一些端口,Socket通讯不上造成的. 我通过cmd →ping localhost ,发现localhost指向::1,这是因为我的系统是win7 ,它支持IPv6的原因.而Eclipse需要lo

SpringMVC,MyBatis项目中兼容Oracle和MySql的解决方案及其项目环境搭建配置、web项目中的单元测试写法、HttpClient调用post请求等案例

 要搭建的项目的项目结构如下(使用的框架为:Spring.SpingMVC.MyBatis): 2.pom.xml中的配置如下(注意,本工程分为几个小的子工程,另外两个工程最终是jar包): 其中pom.xml中的内容如下,其中${ip}为ip地址: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"

在Nodejs中贯彻单元测试

在团队合作中,你写好了一个函数,供队友使用,跑去跟你的队友说,你传个A值进去,他就会返回B结果了.过了一会,你队友跑过来说,我传个A值却返回C结果,怎么回事?你丫的有没有测试过啊? 大家一起写个项目,难免会有我要写的函数里面依赖别人的函数,但是这个函数到底值不值得信赖?单元测试是衡量代码质量的一重要标准,纵观Github的受欢迎项目,都是有test文件夹,并且buliding-pass的.如果你也为社区贡献过module,想更多人使用的话,加上单元测试吧,让你的module值得别人信赖. 要在N

Visual Studio 中的单元测试 UNIT TEST

原文:Visual Studio 中的单元测试 UNIT TEST 注:本文系作者原创,可随意转载,但请注明出处.如实在不愿注明可留空,强烈反对更改原创出处. TDD(Test-Driven Development) 测试驱动开发是敏捷开发中的一项核心实践和技术,也是一种设计方法论.TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码.单元测试是最基本的测试步骤.位于整个产品开发流程V模型的最底部.大致如图,在各种开发流程中RA&PSD完成后,无需底层基础,

TDD中的单元测试写多少才够

测试驱动开发(TDD)已经是耳熟能详的名词,既然是测试驱动,那么测试用例代码就要写在开发代码的前面.但是如何写测试用例?写多少测试用例才够?我想大家在实际的操作过程都会产生这样的疑问. 3月15日,我参加了thoughtworks组织的"结对编程和TDD Openworkshop"活动,聆听了tw的资深咨询专家仝(tong2)键的精彩讲解,并在讲师的带领下实际参与了一次TDD和结对编程的过程.活动中,仝键老师对到底写多少测试用例才够的问题,给出了下面一个解释: 我们写单元测试,有一个重