Android最佳Mock单元测试方案:Junit + Mockito + Powermock

本文旨在从实践出发,引导开发者在Android项目中进行Mock单元测试。

什么是单元测试

单元测试由一组独立的测试构成,每个测试针对软件中的一个单独的程序单元。单元测试并非检查程序单元之间是否能够合作良好,而是检查单个程序单元行为是否正确。

为什么要进行单元测试

在敏捷开发大行其道的今天,由于时间紧,任务重,过分依赖测试工程师以及下列原因,导致单元测试不被重视,在开发流程中处于一个可有可无的尴尬境地。
1. 浪费的时间太多
1. 软件开发人员不应参与单元测试
1. 我是很棒的程序员,不需要进行单元测试
1. 不管怎样,集成测试将会抓住所有的Bug
1. 单元测试效率不高

那么单元测试是否正的可有可无呢?No! No! No!

  1. 作为android客户端研发,在一个开发周期内,你负责的需求需要Web服务(API),和本地代码(JNI,Native Code)的支持,而你们的工作是同时进行的。
  2. 你的需求开发完成了,但是由于需要在特定条件下才能触发,而这些条件在开发过程中很难去模拟,导致需求无法在所有场景下进行充分测试。举个例子,假设你在室内开发一个地图导航的Android应用,你需要在导航过程中,前方出现车祸,积水,施工等多种状况,怎么办?
  3. 总结你过去的BUG,你会发现有些你以为写的很完善的逻辑,却在最后被发现有场景未覆盖,或者逻辑错误等问题。
  4. 测试工程师给你报了一个BUG,你改完提交了,但是之后由于Merge失误导致代码丢失,或者其他人的修改导致你的BUG再次出现。直到测试工程师再次发现该BUG,并再次给你提出。
  5. 你的开发进度很快,但是开发完成后,你会被BUG淹没。你持续不断的修改BUG,持续不断的加班,直至发布版本,身心俱疲。
  6. 以前明明很正常的功能,在本次开发周期内,突然不能正常使用了。
    ...

如果你也经常碰到以上问题,或者困扰,那么你需要持续不断的对项目进行单元测试

Android单元测试简介

Android的单元测试分为两大类:

1.Instrumentation

通过Android系统的Instrumentation测试框架,我们可以编写测试代码,并且打包成APK,运行在Android手机上。

优点: 逼真
缺点: 很慢

代表框架:JUnit(Android自带),espresso

2.JUnit / Mock

通过JUnit,以及第三方测试框架,我们可以编写测试代码,生成class文件,直接运行在JVM虚拟机中。

优点: 很快。使用简单,方便。
缺点: 不够逼真。比如有些硬件相关的问题,无法通过这些测试出来。

代表框架: JUnit(标准),Robolectric, mockito, powermock

Android最佳Mock单元测试方案

我通过对比前辈们对各种单元测试框架的实践,总结出Android最佳Mock单元测试方案: Junit + Mockito + Powermock.(自己认证的...)

Junit + Mockito + Powermock 简介

众所周知,Junit是一个简单的单元测试框架。
Mockito,则是一个简单的用于Mock的单元测试框架。

那么为什么还需要Powermock呢?
EasyMock和Mockito等框架,对static, final, private方法均是不能mock的。
这些框架普遍是通过创建Proxy的方式来实现的mock。 而PowerMock是使用CGLib来操纵字节码而实现的mock,所以它能实现对上面方法的mock。

Junit + Mockito + Powermock 引入

由于PowerMock对Mockito有较强依赖,因此需要按照以下表格采用对应的版本。

Mockito PowerMock
2.0.0-beta - 2.0.42-beta 1.6.5+
1.10.8 - 1.10.x 1.6.2+
1.9.5-rc1 - 1.9.5 1.5.0 - 1.5.6
1.9.0-rc1 & 1.9.0 1.4.10 - 1.4.12
1.8.5 1.3.9 - 1.4.9
1.8.4 1.3.7 & 1.3.8
1.8.3 1.3.6
1.8.1 & 1.8.2 1.3.5
1.8 1.3
1.7 1.2.5

建议方案:
在项目依赖文件build.gradle中添加以下依赖。

testCompile 'junit:junit:4.11'
// required if you want to use Mockito for unit tests
testCompile 'org.mockito:mockito-core:1.9.5'
// required if you want to use Powermock for unit tests
testCompile 'org.powermock:powermock-module-junit4:1.5.6'
testCompile 'org.powermock:powermock-module-junit4-rule:1.5.6'
testCompile 'org.powermock:powermock-api-mockito:1.5.6'

Junit + Mockito + Powermock 配置

  1. 默认的测试代码位置
    对于通过gradle构建的android项目,在默认的项目结构中,Instrumentation的测试代码放在
    src/androidTest/ 目录,而JUnit / Mock的测试代码放在 src/test/ 目录。
  2. 自定义测试代码位置
    有些项目是由Eclipse构建迁移到由Gradle构建,需要自定义测试代码位置。
    举个例子,androidTest和test目录都在项目的根文件夹下。我们需要这样配置:
android {
  sourceSets {
    test {
      java.srcDir 'test'
    }
    androidTest {
      java.srcDir 'androidTest'
    }
  }
}

如果在单元测试中遇到类似"Method ... not mocked."的问题,请添加以下设置:

android {
  // ...
  testOptions {
    unitTests.returnDefaultValues = true
  }
}

Junit + Mockito + Powermock 使用

强烈建议你熟读以下内容,来熟悉Junit + Mockito + Powermock的使用。
1. Mockito 中文文档 ( 2.0.26 beta )
1. Mockito reference documentation
1. powermock wiki
1. Unit tests with Mockito - Tutorial

下面通过举例来简单说明Junit + Mockito + Powermock 使用,更多详情清参考Demo项目:
https://github.com/snowdream/test/tree/master/android/test/mocktest

源码: https://github.com/snowdream/test/blob/master/android/test/mocktest/app/src/main/java/snowdream/github/com/mocktest/Calc.java

测试代码:https://github.com/snowdream/test/blob/master/android/test/mocktest/app/src/test/java/snowdream/github/com/mocktest/CalcUnitTest.java

1.验证某些行为,主要是验证某些函数是否被调用,以及被调用的具体次数。

//using mock
mockedList.add("once");

mockedList.add("twice");
mockedList.add("twice");

mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");

//following two verifications work exactly the same - times(1) is used by default
// 下面的两个验证函数效果一样,因为verify默认验证的就是times(1)
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");

//exact number of invocations verification
// 验证具体的执行次数
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");

//verification using never(). never() is an alias to times(0)
// 使用never()进行验证,never相当于times(0)
verify(mockedList, never()).add("never happened");

//verification using atLeast()/atMost()
// 使用atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");

2.验证执行顺序,主要验证某些函数是否按照预定顺序执行。

// A. Single mock whose methods must be invoked in a particular order
// A. 验证mock一个对象的函数执行顺序
List singleMock = mock(List.class);

//using a single mock
singleMock.add("was added first");
singleMock.add("was added second");

//create an inOrder verifier for a single mock
// 为该mock对象创建一个inOrder对象
InOrder inOrder = inOrder(singleMock);

//following will make sure that add is first called with "was added first, then with "was added second"
// 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");

// B. Multiple mocks that must be used in a particular order
// B .验证多个mock对象的函数执行顺序
List firstMock = mock(List.class);
List secondMock = mock(List.class);

//using mocks
firstMock.add("was called first");
secondMock.add("was called second");

//create inOrder object passing any mocks that need to be verified in order
// 为这两个Mock对象创建inOrder对象
InOrder inOrder = inOrder(firstMock, secondMock);

//following will make sure that firstMock was called before secondMock
// 验证它们的执行顺序
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");

// Oh, and A + B can be mixed together at will

3.使用powermock必须使用两个annotation:

@RunWith(PowerMockRunner.class)
@PrepareForTest({Calc.class})
public class CalcUnitTest {
}
//PrepareForTest 后面要加准备被mock或stub的类,单个class直接()起来即可,多个用{},并用逗号隔开。

4.测试公开成员变量

@Test
public void testPublicField() {
    assertEquals(mCalc.mPublicField, 0);
    assertEquals(mCalc.mPublicFinalField, 0);
    assertEquals(Calc.mPublicStaticField, 0);
    assertEquals(Calc.mPublicStaticFinalField, 0);

    mCalc.mPublicField = 1;
    Calc.mPublicStaticField = 2;

    assertEquals(mCalc.mPublicField, 1);
    assertEquals(mCalc.mPublicFinalField, 0);
    assertEquals(Calc.mPublicStaticField, 2);
}

5.测试公开成员方法

@Test
public void testAddPublicMethod() {
    //when
    when(mCalc.addPublic(anyInt(), anyInt()))
            .thenReturn(0)
            .thenReturn(1)
            .thenReturn(2)
            .thenReturn(3)
            .thenReturn(4)
            .thenReturn(5);

    //call method
    for (int i = 0; i < 6; i++) {

        //verify
        assertEquals(mCalc.addPublic(i, i), i);
    }

    //verify
    verify(mCalc, times(6)).addPublic(anyInt(), anyInt());
    verify(mCalc, atLeast(1)).addPublic(anyInt(), anyInt());
    verify(mCalc, atLeastOnce()).addPublic(anyInt(), anyInt());
    verify(mCalc, atMost(6)).addPublic(anyInt(), anyInt());
}

6.测试公开无返回值成员方法

@Test
public void testAddPublicVoidMethod() {
    //when
    doNothing().when(mCalc).voidPublic(anyInt(), anyInt());

    mCalc.voidPublic(anyInt(), anyInt());
    mCalc.voidPublic(anyInt(), anyInt());

    verify(mCalc, atLeastOnce()).voidPublic(anyInt(), anyInt());
    verify(mCalc, atLeast(2)).voidPublic(anyInt(), anyInt());
}

7.测试公开静态成员方法


    @Test
    public void testAddPublicStaicMethod() throws Exception {
        PowerMockito.mockStatic(Calc.class);

        PowerMockito.when(Calc.class, "addPublicStatic", anyInt(), anyInt())
                .thenReturn(0)
                .thenReturn(1)
                .thenReturn(2)
                .thenReturn(3)
                .thenReturn(4)
                .thenReturn(5);

        //call method
        for (int i = 0; i < 6; i++) {

            //verify
            assertEquals(Calc.addPublicStatic(i, i), i);
        }

        //verify static
        PowerMockito.verifyStatic(times(6));
    }

8.测试私有成员变量
Powermock提供了一个Whitebox的class,可以方便的绕开权限限制,可以get/set private属性,实现注入。也可以调用private方法。也可以处理static的属性/方法,根据不同需求选择不同参数的方法即可。

@Test
public void testPrivateField() throws IllegalAccessException {
    PowerMockito.mockStatic(Calc.class);

    assertEquals(Whitebox.getField(Calc.class, "mPrivateField").getInt(mCalc), 0);
    assertEquals(Whitebox.getField(Calc.class, "mPrivateFinalField").getInt(mCalc), 0);
    assertEquals(Whitebox.getField(Calc.class, "mPrivateStaticField").getInt(null), 0);
    assertEquals(Whitebox.getField(Calc.class, "mPrivateStaticFinalField").getInt(null), 0);

    Whitebox.setInternalState(mCalc, "mPrivateField", 1);
    Whitebox.setInternalState(Calc.class, "mPrivateStaticField", 1, Calc.class);

    assertEquals(Whitebox.getField(Calc.class, "mPrivateField").getInt(mCalc), 1);
    assertEquals(Whitebox.getField(Calc.class, "mPrivateFinalField").getInt(mCalc), 0);
    assertEquals(Whitebox.getField(Calc.class, "mPrivateStaticField").getInt(null), 1);
    assertEquals(Whitebox.getField(Calc.class, "mPrivateStaticFinalField").getInt(null), 0);
}

9.测试私有成员方法

@Test
    public void testAddPrivateMethod() throws Exception {
        PowerMockito.mockStatic(Calc.class);

        //when
        PowerMockito.when(mCalc,"addPrivate",anyInt(),anyInt())
                .thenReturn(0)
                .thenReturn(1)
                .thenReturn(2)
                .thenReturn(3)
                .thenReturn(4)
                .thenReturn(5);

        //call method
        for (int i = 0; i < 6; i++) {

            //verify
            assertEquals(Whitebox.invokeMethod(mCalc,"addPrivate",i,i), i);
        }

        //verify static
        PowerMockito.verifyPrivate(mCalc,times(6)).invoke("addPrivate",anyInt(),anyInt());
        PowerMockito.verifyPrivate(mCalc,atLeast(1)).invoke("addPrivate",anyInt(),anyInt());
    }

10.测试私有静态成员方法

@Test
  public void testAddPrivateStaicMethod() throws Exception {
      PowerMockito.mockStatic(Calc.class);

      PowerMockito.when(Calc.class, "addPrivateStatic", anyInt(), anyInt())
              .thenReturn(0)
              .thenReturn(1)
              .thenReturn(2)
              .thenReturn(3)
              .thenReturn(4)
              .thenReturn(5);

      //call method
      for (int i = 0; i < 6; i++) {

          //verify
          assertEquals(Whitebox.invokeMethod(Calc.class,"addPrivateStatic",i, i), i);
      }

      //verify static
      PowerMockito.verifyStatic(times(6));
  }

通过以上介绍,相信你对Android项目的Mock单元测试有一定的了解。
如果你有任何相关疑问,请通过以下方式联系我:

Email:yanghui1986527#gmail.com
QQ 群: 529327615

参考

  1. 详细讲解单元测试的内容
  2. 浅谈单元测试的意义
  3. 敏捷开发之测试
  4. Unit testing support
  5. junit4
  6. mockito
  7. powermock
  8. mocktest
  9. 在Android Studio中进行单元测试和UI测试
  10. whats-the-best-mock-framework-for-java
  11. mock测试
  12. 使用PowerMock来Mock静态函数
  13. PowerMock介绍
  14. Sharing code between unit tests and instrumentation tests on Android
  15. Unit tests with Mockito - Tutorial
  16. Android单元测试之Mockito浅析
  17. Mockito 简明教程
  18. mockito简单教程
时间: 2024-10-02 13:42:32

Android最佳Mock单元测试方案:Junit + Mockito + Powermock的相关文章

JUnit + Mockito 单元测试(二)(good)

  import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; import java.util.List; import java.util.Map; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.

JUnit + Mockito 单元测试(二)

JUnit 是单元测试框架.Mockito 与 JUnit 不同,并不是单元测试框架(这方面 JUnit 已经足够好了),它是用于生成模拟对象或者直接点说,就是"假对象"的工具.两者定位不同,所以一般通常的做法就是联合 JUnit + Mockito 来进行测试. 入门 首先是配置 Mock 对象,看看例子怎么写的. List mock = mock( List.class ); when( mock.get(0) ).thenReturn( 1 ); assertEquals( &q

《精通移动App测试实战:技术、工具和案例》一2.3 应用JUnit对Android项目进行单元测试

2.3 应用JUnit对Android项目进行单元测试 2.3.1 JUnit基于Android项目TestCase的应用 如果我们对基于Android系统项目开发有一定了解的话,相信一定能看出来,其核心代码是计算两个整数相加的函数.我们做单元测试当然也挑选其最核心的函数来进行测试.可以按照如下的步骤来创建一个基于Android项目的测试用例(TestCase). 第一步:选中"CalculatorOfTwoNum"项目下的"src"目录中的"com.yu

单元测试与JUNIT

前言 考察目前关于单元测试和JUnit的文章,要么是介绍单元测试的理论,要么是通过一个简单的HelloWorld例子介绍工具的使用.这样很容易使读者在实际应用中无从下手.因为只有工具而没有理论的指导,将严重消弱了工具的作用,最终只能是沙滩建楼,达不到预期的目标:只有理论而没有工具的支持,也使得理论难有很好的着力点,最终使理论流于空泛.本文试图通过先讲解单元测试理论,进而将这些理论结合到JUnit的使用当中,最后通过对一个实用的.可以重用的时间操作类采用JUnit进行单元测试来完整阐述单元测试的思

在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

Android常见的Event-driven方案

分析Android常用的Event-driven方案,分析各自的设计思路,使用方式,优势劣势:方便在项目中针对性使用,也为设计自己的EDA(Event-driven Architecture)作铺垫. 1.Listener 最普通的事件驱动设计,观察者实现一个listener,注入到事件生产者中,获取事件的回调. 1.1 解决的问题 将事件产生者,和事件监听者解耦开来,使事件监听者可替换,可增加减少. 1.2 使用例子 //1.实现接口 private listener = new OnXXXL

简单谈谈android studio 的单元测试_Android

面对android studio Run 一次项目要等好几分钟的痛点,不得不研究一下android studio 的单元测试. 其实我的目的很简单,在不对视图进行操作的前提下,测试一些activity 的生命周期,或网络拉取数据的一些处理,比如解析 json 数据啊,做网络请求啊等等,也就是对 Model层的测试.这些不需要操作视图,但在没有单元测试环境下,比如我们网络请求一些数据,Log 打印看看是否请求成功,却又要 利用模拟器或真机Run 一次项目,花费好几分钟,这是不能容忍的. 于是乎,强

Android编程之单元测试实例分析_Android

本文实例讲述了Android编程之单元测试用法.分享给大家供大家参考,具体如下: 在实际开发中,开发android软件的过程需要不断地进行测试.使用Junint测试框架,是正规Android开发的必用技术,在Junint中可以得到组件,可以模拟发送事件和检测程序处理的正确性.单元测试是嵌入到项目中:也可以作为一个单独的项目争对某个具体项目进行测试. 第一步:首先在AndroidManifest.xml中加入下面红色代码: <manifest xmlns:android="http://sc

Android编程之单元测试实例分析

本文实例讲述了Android编程之单元测试用法.分享给大家供大家参考,具体如下: 在实际开发中,开发android软件的过程需要不断地进行测试.使用Junint测试框架,是正规Android开发的必用技术,在Junint中可以得到组件,可以模拟发送事件和检测程序处理的正确性.单元测试是嵌入到项目中:也可以作为一个单独的项目争对某个具体项目进行测试. 第一步:首先在AndroidManifest.xml中加入下面红色代码: <manifest xmlns:android="http://sc