本文介绍了Android单元测试入门所需了解的内容,包括JUnit、Mockito和PowerMock的使用,怎样写MVP中的P层的单元测试以及RxJava相关测试。
《望岳》
岱宗夫如何?齐鲁青未了。
造化钟神秀,阴阳割昏晓。
荡胸生层云,决眦入归鸟。
会当凌绝顶,一览众山小。
-唐,杜甫
前言
最近刚学习了下Android单元测试,看了小创作的中文系列教程,也看了官方的英文教程,刚接触那几天那叫一个痛苦,简直就是陷入了泥潭,接触到了好多框架,并且不清楚什么情况下到底用哪个框架。我们先从最基础的做起,项目目前使用的是MVP架构,先从P层开始做单元测试,由浅入深。之前老说MVP方便单元测试,到底哪方便了,等真开始做单元测试的时候,才真正开始理解MVP架构。首先是我们的P层是不引入任何Android框架代码的(如果引用了Android框架的代码需要引入其它框架才能做测试),之前理解不够,在P层执行Toast(应该在View层执行)操作了。
我们先来看下需要了解的框架:
- JUnit:Java 编程语言的单元测试框架,主要用于断言。
- Mockito:Java界使用最广泛的一个mock框架,mock表示在测试环境中创建一个类的虚假对象方便用于验证。Mockito不支持mock匿名类、final类、静态方法和private方法。
- PowerMock:扩展了Mockito,支持Mock静态、final、私有方法等。
JUnit
仓库地址:https://github.com/junit-team/junit4
添加依赖:1
testCompile 'junit:junit:4.12'
Assert类中比较重要的方法如下:
序号 | 方法和描述 |
---|---|
1 | void assertEquals(boolean expected, boolean actual) 检查两个变量或者等式是否平衡 |
2 | void assertFalse(boolean condition) 检查条件是假的 |
3 | void assertNotNull(Object object) 检查对象不是空的 |
4 | void assertNull(Object object) 检查对象是空的 |
5 | void assertTrue(boolean condition) 检查条件为真 |
6 | void fail() 在没有报告的情况下使测试不通过 |
注意:上面的每一个方法,都有一个重载的方法,可以在前面加一个String类型的参数,表示如果验证失败的话,将用这个字符串作为失败的结果报告。
JUnit 中的注解及含义:
序号 | 注解和描述 |
---|---|
1 | @Test 这个注释说明依附在 JUnit 的 public void 方法可以作为一个测试案例。 |
2 | @Before 有些测试在运行前需要创造几个相似的对象。在 public void 方法加该注释是因为该方法需要在 test 方法前运行。 |
3 | @After 如果你将外部资源在 Before 方法中分配,那么你需要在测试运行后释放他们。在 public void 方法加该注释是因为该方法需要在 test 方法后运行。 |
4 | @BeforeClass 在 public void 方法加该注释是因为该方法需要在类中所有方法前运行。被此注解修饰的方法必须是静态的。 |
5 | @AfterClass 它将会使方法在所有测试结束后执行。这个可以用来进行清理活动。 被此注解修饰的方法必须是静态的。 |
6 | @Ignore 这个注释是用来忽略有关不需要执行的测试的。 |
超时
Junit 提供了一个指定超时参数。如果一个测试用例执行的毫秒数超过了指定的参数值,那么 Junit 将自动将它标记为失败。
1 | @Test(timeout=1000) |
捕获异常
Junit 提供了一个捕获异常的参数。你可以测试代码是否抛出了预期的异常。
参数化
Junit 4 引入了一个新的功能参数化测试。参数化测试允许开发人员使用不同的值反复运行同一个测试。你可以遵循下面的步骤来创建参数化测试。
- 在测试类上添加注解@RunWith(Parameterized.class)。
- 创建一个由 @Parameters 注解的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合。
- 创建一个公共的构造函数,参数个数和类型与提供的数据集合一一对应。
- 为每一列测试数据创建一个实例变量。
- 用实例变量作为测试数据的来源来创建你的测试用例。
1 | public class PrimeNumberChecker { |
运行结果:
Mockito
所谓的mock就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象,以达到两大目的:
- 验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
- 指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
注意:Mockito不支持mock匿名类、final类、静态方法和private方法。
添加依赖:1
2
3
4
5
6repositories {
jcenter()
}
dependencies {
testCompile "org.mockito:mockito-core:+"
}
验证行为
1 | //静态导入 |
stubbing
1 | //你可以mock一个实体类 |
- 默认情况下,所有方法都会有返回值,一个 mock 将返回 null,一个原始/基本类型的包装值或适当的空集。例如,对于一个 int/Integer 就是 0,而对于 boolean/Boolean 就是 false。
- Stubbing 可以被覆盖。
- 一旦 stub,该方法将始终返回一个 stub 的值,无论它被调用多少次。
- stubbing 的顺序是重要的。
参数匹配器
Mockito 验证参数值使用 Java 方式:通过使用 equals() 方法。有时,当需要额外的灵活性,可以使用参数匹配器:
1 | //stubbing using built-in anyInt() argument matcher |
参数匹配器允许灵活的验证或 stubbing。自定义参数的匹配信息,请查看 Javadoc 中 ArgumentMatcher 类。如果你正在使用参数的匹配,所有的参数都由匹配器来提供。
下面的示例演示验证,但同样适用于 stubbing:
1 | verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); |
验证调用次数
1 | //using mock |
times(1) 是默认的,因此,使用的 times(1) 可以显示的省略。
Stubbing void 方法处理异常
1 | doThrow(new RuntimeException()).when(mockedList).clear(); |
有序的验证
1 | // A. Single mock whose methods must be invoked in a particular order |
有序验证是为了灵活,你不必一个接一个验证所有的交互。
此外,您还可以通过创建 InOrder 对象传递只与有序验证相关的 mock 。
验证 mock 上不会发生交互
1 | //using mocks - only mockOne is interacted |
寻找多余的调用
1 | //using mocks |
注意:不建议 verifyNoMoreInteractions() 在每个测试方法中使用。 verifyNoMoreInteractions() 是从交互测试工具包一个方便的断言。只有与它的相关时才使用它。滥用它导致难以维护。
标准创建 mock 方式——使用 @Mock 注解
- 最小化可重用 mock 创建代码
- 使测试类更加可读性
- 使验证错误更加易读,因为字段名称用于唯一识别 mock
1 | public class ArticleManagerTest { |
可以使用内建 runner: MockitoJUnitRunner 或者 rule: MockitoRule
更多详见 MockitoAnnotations
另外也可以通过在类上使用@RunWith(MockitoJUnitRunner.class)来达到相同的效果。
Stubbing 连续调用(迭代器式的 stubbing)
1 | when(mock.someMethod("some arg")) |
回调 Stubbing
Mockito允许使用泛型 Answer 接口。我们建议您只使用thenReturn() 或 thenThrow() 来 stubbing 。但是,如果你有一个需要 stub 到泛型 Answer 接口,这里有一个例子:
1 | when(mock.someMethod(anyString())).thenAnswer(new Answer() { |
doThrow() 家族方法
Stubbing void 方法,需要不同的 when(Object)方法,因为编译器不喜欢括号内无效的方法。在用于 Stubbing void 方法中,doThrow(Throwable…) 代替了 stubVoid(Object)。主要原因是提高可读性和与 doAnswer() 保持一致性。
当你想用 stub void 方法时,使用 doThrow():
1 | doThrow(new RuntimeException()).when(mockedList).clear(); |
在调用 when() 的相应地方可以使用 doThrow(), doAnswer(), doNothing(), doReturn() 和 doCallRealMethod(),当stub void 方法和stub 方法在 spy 对象时(见下面),可以不止一次的 stub 相同的方法,在测试的中期来改变 mock 的行为。在所有的 stubbing 调用时,你会更加倾向于使用这些方法来代替 when()。
spy
spy可以实现调用对象的默认实现。
如果不指定mock方法的特定行为,一个mock对象的所有非void方法都将返回默认值:int、long类型方法将返回0,boolean方法将返回false,对象方法将返回null等等;而void方法将什么都不做。
区别:spy对象的方法默认调用真实的逻辑,mock对象的方法默认什么都不做,或直接返回默认值。
1 | //假设目标类的实现是这样的 |
捕获参数
ArgumentCaptor类允许我们在verification期间访问方法的参数。得到方法的参数后我们可以使用它进行测试。
1 | @Captor |
PowerMock
仓库地址:https://github.com/powermock/powermock
1 | //添加依赖 |
PowerMock扩展了EasyMock和Mockito框架,增加了对static和final方法mock支持等功能。
PowerMock有两个重要的注解:
- @RunWith(PowerMockRunner.class)
- @PrepareForTest( { YourClassWithEgStaticMethod.class })
如果你的测试用例里没有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。
普通Mock: Mock参数传递的对象
1 | public boolean callArgumentInstance(File file) { |
说明:普通Mock不需要加@RunWith和@PrepareForTest注解。
Mock方法内部new出来的对象
1 | public class ClassUnderTest { |
说明:当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。
Mock普通对象的final方法
1 | public class ClassUnderTest { |
说明: 当需要mock final方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是final方法所在的类。
Mock普通类的静态方法
1 | public class ClassUnderTest { |
说明:当需要mock静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。
Mock RxJava
1 | @RunWith(PowerMockRunner.class) |
Mock 私有方法
1 | public class ClassUnderTest { |
说明:和Mock普通方法一样,只是需要加注解@PrepareForTest(ClassUnderTest.class),注解里写的类是私有方法所在的类。
Mock系统类的静态和final方法
1 | public class ClassUnderTest { |
说明:和Mock普通对象的静态方法、final方法一样,只不过注解@PrepareForTest里写的类不一样 ,注解里写的类是需要调用系统方法所在的类。
PowerMock 简单实现原理
- 当某个测试方法被注解@PrepareForTest标注以后,在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,然后加载该测试用例使用到的类(系统类除外)。
- PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。
- 如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。
测试RxJava
TestSubscriber用于单元测试,你可以用来执行断言,检查接收的事件,或者包装一个被mock的Subscriber。可以通过RxJava提供的Hook的方式修改线程为立即执行。
1 | public Observable<String> getTestRxJava() { |
图为TestSubscriber类中提供的方法。
参考
- http://robolectric.blogspot.sg/2013/04/the-test-lifecycle-in-20.html
- http://www.jianshu.com/p/3c6fae150346
- http://wiki.jikexueyuan.com/project/junit/
- http://chriszou.com/2016/04/29/android-unit-testing-mockito.html
- http://chriszou.com/2016/04/18/android-unit-testing-junit.html
- http://static.javadoc.io/org.mockito/mockito-core/2.8.47/org/mockito/Mockito.html
- https://waylau.com/mockito-quick-start/
- https://my.oschina.net/jackieyeah/blog/157076