android 单元测试(3)插庄单元测试与Mockito
1.官方文档
android 文档 https://developer.android.google.cn/training/testing/unit-testing/instrumented-unit-tests?hl=zh-cn
示例 https://github.com/android/testing-samples/tree/master/unit/BasicUnitAndroidTest
mockit官网 https://site.mockito.org
mockit api https://javadoc.io/doc/org.mockito/mockito-core/latest/index.html
mockit源码 https://github.com/mockito/mockito
mockit教程 https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
kotlin 示例 https://github.com/mockito/mockito-kotlin
hamcrest http://hamcrest.org https://github.com/hamcrest/JavaHamcrest matcher匹配器断言库
插桩单元测试通常在设备或模拟器上运行 .插桩测试提供的保真度比本地单元测试要高,但运行速度要慢得多。建议只有在必须针对真实设备的行为进行测试时才使用插桩单元测试。如果测试依赖于自己的库,可以使用模拟框架(如 Mockito)来模拟依赖项。
2.简单示例
2.1 引入库
1 dependencies { 2 ... 3 // Optional -- mockito 4 // testImplementation "org.mockito:mockito-core:4.2.0" 5 testImplementation "org.mockito:mockito-inline:4.2.0" 6 androidTestImplementation "org.mockito:mockito-android:4.2.0" 7 }
- testImplementation : 为
test
目录内源码添加依赖 - androidTestImplementation : 为
androidTest目录内源码
添加依赖 - androidTestImplementation 要依赖 "org.mockito:mockito-android:4.2.0" 而不是 "org.mockito:mockito-core:4.2.0"
- 使用 org.mockito:mockito-inline:4.2.0 后,不用在手写MockMaker扩展文件。就可以模拟final、enum、kotlin等的对象。
在应用的模块级 build.gradle
文件中设置AndroidJUnitRunner 为默认插桩测试运行程序.
1 android { 2 defaultConfig { 3 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 4 } 5 }
2.2 示例代码
1 @Test @SmallTest 2 fun test_mock_kotlin(){ 3 // given 4 open class Student (var name : String = "null"){ fun run(){ /*...*/ } } 5 6 // mock creation 7 val student = Mockito.mock(Student::class.java) 8 9 // when 10 Mockito.`when`(student.name).thenReturn("li4") 11 Mockito.doNothing().`when`(student).run() 12 // ... other 13 student.run() 14 15 // then 16 assertTrue(student.name == "li4" ) 17 }
模拟对象创建后,成员值都是类型对应的默认值 [ 0 , false , 空集合,null ],在使用when().then()系列函数给某成员打桩后,该成员才有意义。
2.3 注意事项
- Do not mock types you don’t own
- Don’t mock value objects
- Don’t mock everything
- Show love with your tests!
3.常用api简介
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
3.1 对象创建相关
3.1.1 mock()
作用 : [ 根据类型(类,接口),创建一个模拟对象。 ]
基础版本示例:
1 @Test @SmallTest 2 fun test_mock_1(){ 3 // given 4 open class Student (var name : String = "null",var age : Int = 0) 5 // mock creation 6 val stu1 = Mockito.mock(Student::class.java) 7 val stu2 = Mockito.mock(Student::class.java,"stu2") 8 9 // when 10 Mockito.`when`(stu1.name).thenReturn("NAME" ) 11 Mockito.`when`(stu2.age).thenReturn(16 ) 12 // then 13 assertTrue(stu1.name == "NAME" ) 14 assertTrue(stu2.age == 16 ) 15 }
指定所有成员函数默认行为的版本: mock(Class
1 @Test @SmallTest 2 fun test_mock_answer(){ 3 // given 4 open class Student (){ fun run() = 10} 5 // mock creation 6 val stu1 = Mockito.mock(Student::class.java,CALLS_REAL_METHODS) 7 // CALLS_REAL_METHODS 8 assertTrue(stu1.run() == 10 ) 9 // when 10 Mockito.`when`(stu1.run()).thenReturn(30) 11 // then 12 assertTrue(stu1.run() == 30 ) 13 }
其中 CALLS_REAL_METHODS 就是Mockito 预先定义好的 answer,表示 返回它在类定义时返回的值 ,更多: Answer
自定义配置的版本: mock(Class
1 @Test @SmallTest 2 fun test_mock_settings(){ 3 // given 4 class Student(){ fun value1() = -1 fun copy() = Student()} 5 val setting = Mockito.withSettings().name("MSettings").defaultAnswer(RETURNS_SMART_NULLS) 6 7 val stu1 = Mockito.mock(Student::class.java, setting) 8 9 // when 10 Mockito.`when`(stu1.value1()).thenReturn(100 ) 11 12 // then 13 assertTrue("stubbed",stu1.value1() == 100 ) 14 try { 15 println("stu1.copy() = ${stu1.copy().value1()}") 16 }catch (e1 : SmartNullPointerException){ 17 println("SmartNullPointerException") 18 }catch (e2 : NullPointerException){ 19 println("NullPointerException") 20 }catch (e3 : Exception){ 21 println("Exception") 22 } 23 }
- 第5行指定了一个配置(指定了名字和answer),第7行用它构造。
- 结果:copy() 会抛出 SmartNullPointerException 异常。
3.1.2 mockStatic()
用来模拟一个静态类,不是模拟一个类的静态成员。
1 open class StuStatic1(){ companion object{ 2 @JvmStatic fun value1() = -1 3 @JvmStatic fun copy() = StuStatic1() } 4 } 5 object StuStatic2{ 6 @JvmStatic fun value1() = -1 7 @JvmStatic fun copy() = StuStatic1() 8 } 9 @Test @SmallTest 10 fun test_mock_static(){ 11 // given 12 val stu2 = Mockito.mockStatic(StuStatic2::class.java, RETURNS_SMART_NULLS) 13 // when 14 Mockito.`when`(StuStatic2.value1()).thenReturn(100 ) 15 // then 16 assertTrue(StuStatic2.value1() == 100 ) //ok 17 18 // given 19 val stu1 = Mockito.mockStatic(StuStatic1::class.java, RETURNS_SMART_NULLS) 20 // when 21 Mockito.`when`(StuStatic1.value1()).thenReturn(200 ) 22 // then 23 assertTrue(StuStatic1.value1() == 200 ) //failed 24 }
3.1.3 spy() 部分模拟
- spy 的参数是真实对象,mock 的参数是 class。
- spy 生成的对象,调用其方法时默认会走真实方法。mock对象默认伪值(0,fasle,null,空集合)。
1 @Test @SmallTest 2 fun test_mock_spy(){ 3 // given 4 open class Student () { fun value1() = 1 fun value2() = 2 } 5 6 //spy 7 val stu = Student() 8 val spy = Mockito.spy(stu) 9 10 //when 11 Mockito.`when`(spy.value1()).thenReturn(100) 12 13 //then 14 assertTrue(spy.value1() == 100 ) //ok 15 assertTrue(spy.value2() == 12 ) //failed 16 }
结果:第14行通过,15行失败(因为value2()未打桩,调用的是实际版本value() = 2)。
3.1.4 reset()
重置模拟对象到初始状态,取消全部打桩:
- mock 的对象 : 函数返回 0、false、空集合、null等
- spy的 对象 : 真实函数被调用
- 它的参数是个变参:
void reset(T... mocks) ,调用时可传多个同类型实参 。
1 @Test @SmallTest 2 fun test_mock_reset(){ 3 // given 4 open class Student{ fun value1() = 1 } 5 6 //mock、spy 7 val mock = Mockito.mock(Student::class.java) 8 val spy = Mockito.spy(Student()) 9 10 // when 11 Mockito.`when`(mock.value1()).thenReturn(100) 12 Mockito.`when`(spy.value1()).thenReturn(200 ) 13 14 // then 15 assertTrue(mock.value1() == 100 ) //ok 16 assertTrue(spy.value1() == 200 ) //ok 17 18 // reset(T... mocks) 变参,支持同时多个 19 Mockito.reset(mock,spy) 20 assertTrue(mock.value1() == 0 ) //ok 21 assertTrue(spy.value1() == 1 ) //ok 22 }
3.2 打桩相关
3.2.1 when()
用来指定要打桩的函数
1 @Test @SmallTest 2 fun test_mock_when(){ 3 // given 4 open class Student{ fun run() = 10} 5 val stu1 = Mockito.mock(Student::class.java,RETURNS_SMART_NULLS) 6 // when 7 Mockito.`when`(stu1.run()).thenReturn(30) 8 // then 9 assertTrue(stu1.run() == 30 ) 10 }
3.2.2 thenReturn()
thenReturn 用来指定特定函数的返回值,有两个重载版本:
- OngoingStubbing
thenReturn(T value); - OngoingStubbing
thenReturn(T value, T... values); 变参版本,当仅指定1个时:表示每次都返回该值;当指定多个时:分别对应每1次调用、第2次调用、第n次,超出后使用默认。
1 @Test @SmallTest 2 fun test_mock_then(){ 3 // given 4 open class Student{ fun run() = 10} 5 val stu1 = Mockito.mock(Student::class.java) 6 // when 7 Mockito.`when`(stu1.run()).thenReturn(20,30,40) 8 // then 9 assertTrue(stu1.run() == 20 ) 10 assertTrue(stu1.run() == 30 ) 11 assertTrue(stu1.run() == 40 ) 12 assertTrue(stu1.run() == 0 ) 13 }
这个thenReturh指定了3次的返回值,第9,10,11,行调用是分别使用对应的打桩值,
第12行调用的时候,打桩值已经用完,这时用的默认值。
3.2.3 thenAnswer()
自定义打桩函数的回调用,指定该函数的返回值。语法 : when(mock.fun()).thenAnswer(answer) ,不与thenReturn一起使用。
1 @Test @SmallTest 2 fun test_mock_answer2(){ 3 // given 4 open class Student{ fun value() = 10} 5 val stu1 = Mockito.mock(Student::class.java) 6 7 // when 8 Mockito.`when`(stu1.value()).thenAnswer(object : Answer{ 9 override fun answer(invocation: InvocationOnMock?): Int { 10 val args = invocation?.arguments 11 val mock = invocation?.mock 12 //... 13 return 20 14 } 15 }) 16 // then 17 assertTrue(stu1.value() == 20 ) //ok 18 19 // when 20 Mockito.`when`(stu1.value()).thenAnswer({ invocation -> /* Lambda */ 21 val args = invocation?.arguments 22 val mock = invocation?.mock 23 30 24 }) 25 // then 26 assertTrue(stu1.value() == 30 ) //ok 27 28 // when 29 Mockito.`when`(stu1.value()).thenReturn(40).thenAnswer({ invocation -> 30 50 31 }) 32 assertTrue(stu1.value() == 50 ) //failed. value = 40 33 }
第29行,threnReturn与thenAnswer一起使用了,这时stu1.value() 返回40,且,answer未起作用。
3.2.4 thenThrow()
自定义打桩函数在调用时抛出的异常.
1 @Test @SmallTest 2 fun test_mock_throw(){ 3 // given 4 open class Student{ fun run() = 10 fun stop() = Unit} 5 val stu1 = Mockito.mock(Student::class.java) 6 7 try { 8 Mockito.`when`(stu1.run()).thenThrow(NullPointerException()) 9 stu1.run() 10 }catch (e : NullPointerException){ 11 e.printStackTrace() 12 } 13 14 try { 15 Mockito.doThrow(NullPointerException()).`when`(stu1).stop() 16 stu1.stop() 17 }catch (e : NullPointerException){ 18 e.printStackTrace() 19 } 20 }
注意第8 行和第15行的区别。
3.2.5 doNothing()系列
doReturn() doThrow() doAnswer() doNothing() doCallRealMethod()等
这些函数用来指定mock对象的成员函数为void时的行为,示例如下:
1 @Test @SmallTest //void 2 fun test_mock_void(){ 3 // given 4 open class Student{fun voidFun() {} } 5 // mock creation 6 val mock = Mockito.mock(Student::class.java) 7 8 // 测试doNothing 9 Mockito.doNothing().`when`(mock).voidFun() 10 mock.voidFun() 11 Mockito.verify(mock).voidFun() 12 13 // 测试 doThrow 14 Mockito.reset(mock) 15 Mockito.doThrow(NullPointerException("void")).`when`(mock).voidFun() 16 try { 17 mock.voidFun() 18 }catch (e : NullPointerException){ 19 e.printStackTrace() 20 }finally { 21 Mockito.verify(mock).voidFun() 22 } 23 24 // 测试 doAnswer 25 Mockito.reset(mock) 26 val answer = object : Answer{ 27 override fun answer(invocation: InvocationOnMock?) { 28 println("void function .answer.") 29 } 30 } 31 Mockito.doAnswer(answer).`when`(mock).voidFun() 32 mock.voidFun() 33 Mockito.verify(mock, times(1)).voidFun() 34 }
注意doXX要在when前,且when内的参数是mock对象不是成员函数。
3.3 形参匹配器
3.3.1 作用
在 Mockito 中,有一系列函数:anyInt()、anyBoolean()、any(Class
- list.get(0),精确的参数,表示list中第1个元素
- list.get(anyInt()) ,用来模糊通配一个形参,这里表示list中任意元素,即使超过最大数量,如 list.get(9999999).
- 如果通一个函数的参数经多次匹配(精确匹配或模糊匹配 ),mockito优先选择最新声明的匹配。
3.3.2 示例
1 @Test @SmallTest //形参匹配器,基本类型,类类型,变参。 2 fun test_mock_args(){ 3 // given 4 open class Student(){ 5 fun value1(x : Student,y : Int ) = y 6 fun value2(x : Student? ) {} 7 fun varargs(vararg names :String) = names.size 8 } 9 10 // mock creation 11 val mock = Mockito.mock(Student::class.java) 12 13 // when 14 // 基本类型,类类型, 15 Mockito.`when`(mock.value1(any() ?: Student(),anyInt())).thenReturn(1 ) 16 Mockito.doNothing().`when`(mock).value2(any()) 17 //Mockito.`when`(mock.value1(same(mock),anyInt())).thenReturn(1 ) 18 //Mockito.`when`(mock.value1(isA(Student::class.java),anyInt())).thenReturn(1 ) 19 20 // 变参 21 Mockito.`when`(mock.varargs(any() ?: String())).thenCallRealMethod() 22 23 // then 24 assertTrue(mock.value1(Student(),2) == 1 ) 25 assertTrue(mock.varargs("li","zhang","wang","zhao") == 4 ) 26 }
- 函数value1 有一个类类型及基本类型。函数 varargs 参数是变参。
- 这些静态函数定义在ArgumentMatchers类中,但也可以用 Mockito.anyInt() 这种方式调用。
- 如果上述代码中value1 参数是非null的,由于ArgumentMatchers.any() 返回null,运行时会出错,解决方案参考 这里 ,代码如下:https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/ArgumentMatchers.html
Method and Description any()
Matches anything, including nulls and varargs.any(Class type) anyInt()
Any int or non-nullInteger
.anyList()
anyIterable()
Any non-nullIterable
.anyLong() anyString() anyMap() argThat(ArgumentMatcher
Allows creating custom argument matchers.matcher) byteThat(ArgumentMatcher<Byte> matcher) booleanThat(ArgumentMatcher<Boolean> matcher)
Allows creating customboolean
argument matchers.contains(String substring) intThat(ArgumentMatcher<Integer> matcher) endsWith(String suffix) isNotNull()
Notnull
argument.isNull() eq(int value)
int
argument that is equal to the given value.eq(float value) ... ... 3.3.4 自定义匹配器
https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/ArgumentMatcher.html
https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/ArgumentCaptor.html
作用:
在某些场景中,不光要对方法的返回值和调用进行验证,还需要验证一系列交互后所传入方法的参数。可以用实参捕获器来捕获传入方法的参数,然后对其进行验证。
api:
- ArgumentCaptor.forClass() 构造captor.
- capture() 捕获参数
- getValue() 返回参数
- getAllValues 返回全部参数值。
示例:
say()的参数是可为null的,run()的参数是非null的。play()参数是变参。
1 @Test @SmallTest 2 fun test_mock_captor(){ 3 4 // given 5 open class Student(var name : String = ""){ 6 fun say(word : String?) { } 7 fun run(x : Student) { } 8 fun play(vararg student: Student? ) = student.size 9 } 10 val mock = Mockito.mock(Student::class.java) 11 12 // 可空参数 13 val captor = ArgumentCaptor.forClass(String::class.java) 14 Mockito.doNothing().`when`(mock).say(anyString()) 15 mock.say("hello") ; mock.say("world") 16 Mockito.verify(mock, Mockito.atLeastOnce()).say(captor.capture()) 17 assertTrue(captor.value == "world") 18 19 // 非空参数类对象 20 val captor2 = ArgumentCaptor.forClass(Student::class.java) 21 Mockito.doNothing().`when`(mock).run(any(Student::class.java) ?: Student("")) 22 mock.run(Student("li")) 23 Mockito.verify(mock).run(captor2.capture() ?: Student("")) 24 assertTrue(captor2.value.name == "li") 25 26 // 变参 27 val captor3 = ArgumentCaptor.forClass(Student::class.java) 28 Mockito.`when`(mock.play(any(Student::class.java) ?: Student(""))).thenCallRealMethod() 29 mock.play(Student("wang"),Student("zhao"),Student("zhang")) 30 Mockito.verify(mock).play(captor3.capture()) 31 32 assertTrue(captor3.allValues.size == 3) 33 assertTrue(captor3.allValues[1].name == "zhao") 34 35 }
3.5.5 lenient()
官方文档:
https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/Mockito.html#lenient--
Mockito对存根或者模拟对象有3种检查模式 : STRICT_STUBS(默认) 、WARN、LENIENT ,可以通过如下语句修改
@Rule val mockito : MockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS)
当一个存根被修饰成lenient(),那么该存根检查为宽松模式,它的潜在问题被忽略,如:多余的存根UnnecessaryStubbingException、参数不匹配 等等.
忽略某个存根用:Mockito.lenient() ,
忽略mock对象用:MockSettings.lenient()
示例如下:
1 //@Rule val mockito : MockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS) 2 @Test @SmallTest 3 fun test_mock_lenient(){ 4 5 // given 6 open class Student{fun value1() = 1 fun value2() = 2 fun value3() = 3} 7 8 // mock 9 val mock = Mockito.mock(Student::class.java) 10 // when 11 Mockito.lenient().`when`(mock.value1()).thenReturn(10) 12 // then 13 assertTrue(mock.value1() == 10) 14 assertTrue(mock.value2() == 0 ) 15 16 // mock 17 val mock2 = Mockito.mock(Student::class.java, Mockito.withSettings().lenient()) 18 // when 19 Mockito.`when`(mock2.value1()).thenReturn(10) 20 // then 21 assertTrue(mock2.value1() == 10) 22 assertTrue(mock2.value3() == 3 ) 23 }
4.常用注解
4.1 @Mock
作用:
用注解的方式简化代码,生成模拟对象。不用Mockito.mock(xxx.class)
步骤:
- 设置测试类的JUnitRunner 改为 MockitoJUnitRunner
- 使用@Mock 注解成员变量
- 在@Test函数之前运行openMocks初始化注解,一般放在@Before内,在init内openMock无效、在@Test函数第1句也无效
- 使用测试语句when..then...verify/assert等。
示例:
1 @RunWith(MockitoJUnitRunner::class) 2 class KotlinMockAnnotations{ 3 4 open class MockTest{fun value1() = 1 fun value2() = 2 fun value3() = 3} 5 6 @Mock lateinit var mock : MockTest 7 @Before fun setup() { MockitoAnnotations.openMocks(mock) } 8 // init { MockitoAnnotations.openMocks(mock) }/*init内openMock无效*/ 9 10 @Test @SmallTest 11 fun test_mock_annotations(){ 12 //MockitoAnnotations.openMocks(mock) //在@Test函数第1句也无效 13 14 // when 15 Mockito.`when`(mock.value1()).thenReturn(10) 16 17 // then 18 assertTrue(mock.value1() == 10) //ok 19 assertTrue(mock.value3() == 0 ) //ok 20 assertTrue(mock.value2() == 2 ) //failed value2 = 0 21 } 22 /*@Test 23 fun test_mock_annotations2(@Mock mock2 : MockTest){ //这个@Mock无法运行 24 // when 25 Mockito.`when`(mock2.value1()).thenReturn(10) 26 27 // then 28 assertTrue(mock2.value1() == 10) //ok 29 }*/ 30 }
4.2 @Captor
作用:
简化生成ArgumentCaptor ,关于ArgumentCaptor 详见:
示例:
4.3 @Spy
4.4 @InjectMocks
5.其它特性
5.1 序列化 serializable
5.2 timeout
5.3 abstract classes
5.4 static methods
5.5 object construction
5.6 kotlin 拓展、
https://stackoverflow.com/questions/59230041/argumentmatchers-any-must-not-be-null
5.6 模板
UnnecessaryStubbingException