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 classToMock, Answer defaultAnswer)

 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 classToMock, MockSettings mockSettings)

 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 type)、anyCollection()、eq(char value)、byteThat(ArgumentMatcher matcher)等,它们就是形参匹配器。用来给函数的形参指定一个模糊的实参,而不是一个具体的参数。如 :

  • 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-null Integer. anyList()
    anyIterable()Any non-null Iterable. anyLong()
    anyString() anyMap()
    argThat(ArgumentMatcher matcher) Allows creating custom argument matchers. byteThat(ArgumentMatcher<Byte> matcher)
    booleanThat(ArgumentMatcher<Boolean> matcher) Allows creating custom boolean argument matchers. contains(String substring) 
    intThat(ArgumentMatcher<Integer> matcher) endsWith(String suffix)  
    isNotNull() Not null 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(默认) 、WARNLENIENT  ,可以通过如下语句修改

     @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

相关