相关文章推荐
乖乖的芹菜  ·  粪土当年万户侯,万户侯是什么,侯又是什么?_主公·  9 月前    · 
温暖的凉茶  ·  背靠大树的赫罗纳,不做被看低的“小曼城”_米 ...·  1 年前    · 
旅行中的荒野  ·  无颜之月讲的什么 – 快看漫画问答·  1 年前    · 
老实的刺猬  ·  读者主要看的是剧情还是文笔,还是都看? - 知乎·  2 年前    · 
买醉的饭盒  ·  十四届全国人大一次会议首场“部长通道” ...·  2 年前    · 
Code  ›  #Android单元测试学习总结「建议收藏」开发者社区
mock 函数调用 junit单元测试 android单元测试
https://cloud.tencent.com/developer/article/2096430
暴走的大熊猫
1 年前
全栈程序员站长

#Android单元测试学习总结「建议收藏」

前往小程序,Get 更优 阅读体验!
立即前往
腾讯云
开发者社区
文档 建议反馈 控制台
首页
学习
活动
专区
工具
TVP
最新优惠活动
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
全栈程序员站长
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
社区首页 > 专栏 > #Android单元测试学习总结「建议收藏」

#Android单元测试学习总结「建议收藏」

作者头像
全栈程序员站长
发布 于 2022-09-06 15:55:39
5K 0
发布 于 2022-09-06 15:55:39
举报
文章被收录于专栏: 全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

文章目录

      • 一、本地单元测试
        • 1. 创建测试类
        • 2. Assert类中的常用断言方法
        • 3. 运行测试类
        • 4. 运行单个测试方法或多个测试类
      • 二、Mockito测试框架的使用
        • 1. Mock概念的理解
        • 2. Mockito中几种Mock对象的方式
        • 3. 验证行为
          • verify(T mock)函数的使用
          • 使用`when(T methodCall)`函数
          • 使用`thenAnswer`为回调做测试桩
          • 使用`doCallRealMethod()`函数来调用某个方法的真实实现方法
          • 使用`doNothing()`函数是为了设置void函数什么也不做
          • 使用`doAnswer()`函数测试void函数的回调
          • 需要使用doReturn函数代替thenReturn的情况
          • 使用`doThrow()`函数来测试void函数抛出异常
        • 4. 验证方法的调用次数
        • 5. 参数匹配器 (matchers)
        • 6. 使用InOrder验证执行执行顺序
        • 7. 使用Spy监控真实对象
        • 8. 使用ArgumentCaptor进行参数捕获
        • 9. 使用@InjectMocks自动注入依赖对象
      • 三、PowerMockito框架使用
        • 1. 普通Mock的方式
        • 2. Mock方法内部new出来的对象
        • 3. Mock普通对象的final方法
        • 4. Mock普通类的静态方法
        • 5. verify静态方法的调用次数
        • 6. 使用真实返回值
        • 7. Mock私有方法
        • 8. Mock普通类的私有变量
        • 9. 对静态void方法进行Mock
        • 10. Mock系统的final静态类
      • 四、Robolectric测试框架的使用
      • 五、Espresso测试框架的使用

Android单元 测试 主要分为以下两种

  • 本地单元测试(Junit Test), 本地单元测试是纯java代码的测试,只运行在本地电脑的JVM环境上,不依赖于Android框架的任何api, 因此执行速度快,效率较高,但是无法测试Android相关的代码。
  • 仪器化测试(Android Test),是针对Android相关代码的测试,需要运行在真机设备或模拟器上,运行速度较慢,但是可以测试UI的交互以及对设备信息的访问,得到接近真实的测试结果。

在Android Studio中新建一个项目的时候, app 的 gradle 中会默认添加单元测试的相关依赖库:

代码语言: javascript
复制
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }

其中 testImplementation 添加的依赖就是本地化测试库, androidTestImplementation 添加的依赖则是Android环境下的测试库,同时,在项目的工程目录下也会默认创建好测试的目录:

#Android单元测试学习总结「建议收藏」
#Android单元测试学习总结「建议收藏」

其中 app/src/test/ 下面存放的是Junit本地测试代码, app/src/androidTest/ 下面存放的是Android测试代码。

一、本地单元测试

进行本地单元测试需要先了解一些基本的Junit注解:

注解名称

含义

@Test

定义所在方法为单元测试方法,方法必须是public void

@Before

定义所在方法在每个测试用例执行之前执行一次, 用于准备测试环境(如: 初始化类,读输入流等),在一个测试类中,每个@Test方法的执行都会触发一次调用

@After

定义所在方法在每个测试用例执行之后执行一次,用于清理测试环境数据,在一个测试类中,每个@Test方法的执行都会触发一次调用。

@BeforeClass

定义所在方法在测试类里的所有用例运行之前运行一次,方法必须是public static void,用于做一些耗时的初始化工作(如: 连接数据库)

@AfterClass

定义所在方法在测试类里的所有用例运行之后运行一次,方法必须是public static void,用于清理数据(如: 断开数据连接)

@Test (expected = Exception.class)

如果该测试方法没有抛出Annotation中的Exception类型(子类也可以),则测试失败

@Test(timeout=100)

如果该测试方法耗时超过100毫秒,则测试失败,用于性能测试

@Ignore 或者 @Ignore(“太耗时”)

忽略当前测试方法,一般用于测试方法还没有准备好,或者太耗时之类的

@FixMethodOrder

定义所在的测试类中的所有测试方法都按照固定的顺序执行,可以指定3个值,分别是DEFAULT、JVM、NAME_ASCENDING(字母顺序)

@RunWith

指定测试类的测试运行器

更多可以参考Junit官网: https://junit.org/junit4/

1. 创建测试类

接下来就可以创建测试类,除了可以手动创建测试类外,可以利用AS快捷键:将光标选中要创建测试类的类名上->按下ALT + ENTER->在弹出的弹窗中选择Create Test

#Android单元测试学习总结「建议收藏」
#Android单元测试学习总结「建议收藏」

这会弹出下面的弹窗,或者鼠标在类名上右键选择菜单Go to–>Test,也会弹出下面的弹窗

#Android单元测试学习总结「建议收藏」
#Android单元测试学习总结「建议收藏」

勾选需要进行测试的方法,会自动生成一个测试类:

#Android单元测试学习总结「建议收藏」
#Android单元测试学习总结「建议收藏」

如果勾选了 @Before 或 @After 的话也会自动给你生成对应的测试方法

接下来编写测试方法,首先在要测试的目标类中写几个业务方法:

代码语言: javascript
复制
public class SimpleClass { public boolean isTeenager(int age) { if (age < 15) { return true; } return false; } public int add(int a, int b) { return a + b; } public String getNameById(int id) { if (id == 1) { return "小明"; } else if (id == 2){ return "小红"; } return ""; } }

然后,测试类:

代码语言: javascript
复制
@RunWith(JUnit4.class) public class SimpleClassTest { private SimpleClass simpleClass; @Before public void setUp() throws Exception { simpleClass = new SimpleClass(); } @After public void tearDown() throws Exception { } @Test public void isTeenager() { Assert.assertFalse(simpleClass.isTeenager(20)); Assert.assertTrue(simpleClass.isTeenager(14)); } @Test public void add() { Assert.assertEquals(simpleClass.add(3, 2), 5); Assert.assertNotEquals(simpleClass.add(3, 2), 4); } @Test public void getNameById() { Assert.assertEquals(simpleClass.getNameById(1), "小明"); Assert.assertEquals(simpleClass.getNameById(2), "小红"); Assert.assertEquals(simpleClass.getNameById(10), ""); } }

其中 setUp() 是自动生成的添加了 @Before 注解,这会在每个测试方法执行前执行,因此在这里创建一个目标对象,也可以选择添加 @BeforeClass 注解但这时 setUp() 应该改为静态的方法。然后在每个测试方法中编写测试用例,这里使用 org.junit.Assert 包中的断言方法,有很多 assertXXX 方法,可以自己选择用来判断目标方法的结果是否满足预期。

2. Assert类中的常用断言方法

方法

含义

assertNull(Object object)

断言对象为空

assertNull(String message, Object object)

断言对象为空,如果不为空抛出异常携带指定的message信息

assertNotNull(Object object)

断言对象不为空

assertNotNull(Object object)

断言对象不为空,如果为空抛出异常携带指定的message信息

assertSame(Object expected, Object actual)

断言两个对象引用的是同一个对象

assertSame(String message, Object expected, Object actual)

断言两个对象引用的是同一个对象,否则抛出异常携带指定的message信息

assertNotSame(Object expected, Object actual)

断言两个对象引用的不是同一个对象

assertNotSame(String message, Object expected, Object actual)

断言两个对象引用的不是同一个对象,否则抛出异常携带指定的message信息

assertTrue(boolean condition)

断言结果为true

assertTrue(String message, boolean condition)

断言结果为true, 为false时抛出异常携带指定的message信息

assertFalse(boolean condition)

断言结果为false

assertFalse(String message, boolean condition)

断言结果为false, 为true时抛出异常携带指定的message信息

assertEquals(long expected, long actual)

断言两个long 类型 expected 和 actual 的值相等

assertEquals(String message, long expected, long actual)

断言两个long 类型 expected 和 actual 的值相等,如不相等则抛异常携带指定message信息

assertEquals(Object expected, Object actual)

断言两个对象相等

assertEquals(String message, Object expected, Object actual)

断言两个对象相等,如果不相等则抛出异常携带指定的message信息

assertEquals(float expected, float actual, float delta)

断言两个 float 类型 expect 和 actual 在 delta 偏差值下相等,delta是误差精度

assertEquals(String message, float expected, float actual, float delta)

断言两个 float 类型 expect 和 actual 在 delta 偏差值下相等,如果不相等则抛出异常携带指定的message信息

assertEquals(double expected, double actual, double delta)

断言两个 double 类型 expect 和 actual 在 delta 偏差值下相等

assertEquals(String message, double expected,double actual, double delta)

断言两个 double 类型 expect 和 actual 在 delta 偏差值下相等,如果不相等则抛出异常携带指定的message信息

assertArrayEquals(T[] expected, T[] actual)

断言两个相同类型的数组的元素一一对应相等

assertArrayEquals(String message, T[] expected, T[] actual)

断言两个相同类型的数组的元素一一对应相等,如果不相等则抛出异常携带指定的message信息

fail()

直接让测试失败

fail(String message)

直接让测试失败并给出message错误信息

assertThat(T actual, Matcher<? super T> matcher)

断言actual和matcher规则匹配

assertThat(String reason, T actual, Matcher<? super T> matcher)

断言actual和matcher规则匹配,否则抛出异常携带指定的reason信息

其中 assertEquals 的方法,都对应有一个 assertNotEquals 方法,这里不列了, assertThat 是一个强大的方法:

代码语言: javascript
复制
 Assert.assertThat(1, is(1)); Assert.assertThat(0, is(not(1))); Assert.assertThat("hello", startsWith("h")); List<String> items = new ArrayList<>(); items.add("aaa"); items.add("bbb"); Assert.assertThat(items, hasItem("aaa"));

需要静态导入 org.hamcrest.Matchers 类里面的方法,更多匹配方法请参考这个类。

3. 运行测试类

选中测试类右键Run运行,控制面板中就会显示测试结果:

在这里插入图片描述
在这里插入图片描述

如果所有的测试用例都正常返回了预期的结果,则面板中左侧每个测试方法前面会带一个绿色的对勾,否则方法前面会变成红色感叹号并且控制面板会输出异常,现在来改一个业务方法试一下:

代码语言: javascript
复制
    public boolean isTeenager(int age) { if (age < 15) { return false; } return false; }

这里将 age < 15 改为输出false,假设这是我们在编码的时候由于疏忽粗心造成的,然后运行测试类:

在这里插入图片描述
在这里插入图片描述

控制面板会告诉那一行出错了:

在这里插入图片描述
在这里插入图片描述

也就是说这里没有返回预期的结果,说明我们编写的业务逻辑是有错误的,这时就需要改bug了。

4. 运行单个测试方法或多个测试类

上面是运行的整个测试类,如果要运行测试类的单个方法,则鼠标只选中某个要运行的测试方法,然后右键选择Run即可。如果要同时运行多个测试类,而如果多个测试类在同一个包下面,则选中多个测试类所在的包目录,然后右键选择Run运行。否则可以通过下面的方式指定,创建一个空的测试类,然后添加注解:

代码语言: javascript
复制
@RunWith(Suite.class) @Suite.SuiteClasses({SimpleClassTest.class, SimpleClass2Test.class}) public class RunMultiTest { }

运行这个测试类就可以将指定的测试类的方法一起运行。

二、Mockito测试框架的使用

前面介绍的只能测试不涉及Android相关Api的java代码用例,如果涉及到Android相关Api的时候,就不方便了,这时如果不依赖第三方库的话可能需要使用仪器化测试跑到Android设备上去运行,于是有一些比较好的第三方的替代框架可以来模拟使用Android的代码测试,Mockito就是基于依赖注入实现的一个测试框架。

1. Mock概念的理解

什么是Mock, 这个单词的中文意思就是“模仿”或者“虚假”的意思,也就是要模仿一个对象,为啥要模仿? 在传统的JUnit单元测试中,没有消除在测试中对对象的依赖,如A对象依赖B对象方法,在测试A对象的时候,我们需要构造出B对象,这样子增加了测试的难度,或者使得我们对某些类的测试无法实现。这与单元测试的思路相违背。 还有一个主要的问题就是本地单元测试由于是运行本地JVM环境,无法依赖Android的api,只靠纯Junit的测试环境很难模拟出完整的Android环境,导致无法测试Android相关的代码,而Mock就能解决这个问题,通过Mock能够很轻易的实现对象的模拟。

添加依赖:

代码语言: javascript
复制
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'org.mockito:mockito-core:2.19.0' .... }
2. Mockito中几种Mock对象的方式

使用之前通过静态方式导入会使用更方便:

代码语言: javascript
复制
 // 静态导入会使代码更简洁 import static org.mockito.Mockito.*;

直接mock一个对象:

代码语言: javascript
复制
    @Test public void testMock() { SimpleClass mockSimple = Mockito.mock(SimpleClass.class); assertNotNull(mockSimple); }

注解方式mock一个对象:

代码语言: javascript
复制
    @Mock SimpleClass simple; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void testMock() { assertNotNull(simple); }

运行器方式mock一个对象:

代码语言: javascript
复制
@RunWith(MockitoJUnitRunner.class) public class ExampleUnitTest { @Mock SimpleClass simple; @Test public void testMock() { assertNotNull(simple); } } 

MockitoRule方式mock一个对象:

代码语言: javascript
复制
public class ExampleUnitTest { @Mock SimpleClass simple; @Rule //<--使用@Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Test public void testMock() { assertNotNull(simple); } }
3. 验证行为
verify(T mock)函数的使用

verify(T mock) 的作用是验证发生的某些行为等同于 verify(mock, times(1)) 例如:

代码语言: javascript
复制
@Test public void testMock() { //创建mock对象 List mockedList = mock(List.class); //使用mock对象 mockedList.add("one"); mockedList.clear(); //验证mockedList.add("one")是否被调用,如果被调用则当前测试方法通过,否则失败 verify(mockedList).add("one"); //验证 mockedList.clear()是否被调用,如果被调用则当前测试方法通过,否则失败 verify(mockedList).clear(); }
代码语言: javascript
复制
@Test public void testMock() { mock.someMethod("some arg"); //验证mock.someMethod("some arg")是否被调用,如果被调用则测试方法通过,否则失败 verify(mock).someMethod("some arg"); }

也就是说如果把调用的方法注释掉,则运行testMock()方法就会失败。

通过 verify 关键字,一旦mock对象被创建了,mock对象会记住所有的交互。然后你就可能选择性的验证你感兴趣的交互。

通常需要配合一些测试方法来验证某些行为,这些方法称为”打桩方法”(Stub),打桩的意思是针对mock出来的对象进行一些模拟操作,如设置模拟的返回值或抛出异常等。

常见的打桩方法:

方法名

方法含义

doReturn(Object toBeReturned)

提前设置要返回的值

doThrow(Throwable… toBeThrown)

提前设置要抛出的异常

doAnswer(Answer answer)

提前对结果进行拦截

doCallRealMethod()

调用某一个方法的真实实现

doNothing()

设置void函数什么也不做

thenReturn(T value)

设置要返回的值

thenThrow(Throwable… throwables)

设置要抛出的异常

thenAnswer(Answer<?> answer)

对结果进行拦截

例如:

代码语言: javascript
复制
 @Test public void testMock() { // 你可以mock具体的类型,不仅只是接口 List mockedList = mock(List.class); // 打测试桩 when(mockedList.get(0)).thenReturn("first"); doReturn("aaaa").when(mockedList).get(1); when(mockedList.get(1)).thenThrow(new RuntimeException()); doThrow(new RuntimeException()).when(mockedList).clear(); // 输出“first” System.out.println(mockedList.get(0)); // 因为get(999) 没有打桩,因此输出null, 注意模拟环境下这个地方是不会报IndexOutOfBoundsException异常的 System.out.println(mockedList.get(999)); // get(1)时会抛出异常 System.out.println(mockedList.get(1)); // clear会抛出异常 mockedList.clear(); }

doXXX 和 thenXXX 使用上差不多,一个是调用方法之前设置好返回值,一个是在调用方法之后设置返回值。默认情况下,Mock出的对象的所有非void函数都有返回值,对象类型的默认返回的是null,例如返回 int、boolean、String 的函数,默认返回值分别是 0、false 和 null 。

使用 when(T methodCall) 函数

打桩方法需要配合 when(T methodCall) 函数,意思是使测试桩方法生效。当你想让这个mock能调用特定的方法返回特定的值,那么你就可以使用它。

例如:

代码语言: javascript
复制
when(mock.someMethod()).thenReturn(10); //你可以使用灵活的参数匹配,例如 when(mock.someMethod(anyString())).thenReturn(10); //设置抛出的异常 when(mock.someMethod("some arg")).thenThrow(new RuntimeException()); //你可以对不同作用的连续回调的方法打测试桩: //最后面的测试桩(例如:返回一个对象:"foo")决定了接下来的回调方法以及它的行为。 when(mock.someMethod("some arg")) .thenReturn("foo")//第一次调用someMethod("some arg")会返回"foo" .thenThrow(new RuntimeException());//第二次调用someMethod("some arg")会抛异常 //可以用以下方式替代比较小版本的连贯测试桩: when(mock.someMethod("some arg")) .thenReturn("one", "two"); //和下面的方式效果是一样的 when(mock.someMethod("some arg")) .thenReturn("one") .thenReturn("two"); //比较小版本的连贯测试桩并且抛出异常: when(mock.someMethod("some arg")) .thenThrow(new RuntimeException(), new NullPointerException();
使用 thenAnswer 为回调做测试桩
代码语言: javascript
复制
when(mock.someMethod(anyString())).thenAnswer(new Answer() { Object answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); Object mock = invocation.getMock(); return "called with arguments: " + args; } }); // 输出 : "called with arguments: foo" System.out.println(mock.someMethod("foo"));
使用 doCallRealMethod() 函数来调用某个方法的真实实现方法

注意,在Mock环境下,所有的对象都是模拟出来的,而方法的结果也是需要模拟出来的,如果你没有为mock出的对象设置模拟结果,则会返回默认值,例如:

代码语言: javascript
复制
public class Person { public String getName() { return "小明"; } } @Test public void testPerson() { Person mock = mock(Person.class); //输出null,除非设置发回模拟值when(mock.getName()).thenReturn("xxx"); System.out.println(mock.getName()); }

因为getName()方法没有设置模拟返回值,而getName()返回值是String类型的,因此直接调用的话会返回String的默认值null,所以上面代码如果要想输出getName()方法的真实返回值的话,需要设置doCallRealMethod():

代码语言: javascript
复制
 @Test public void testPerson() { Person mock = mock(Person.class); doCallRealMethod().when(mock).getName(); //输出“小明” System.out.println(mock.getName()); }
使用 doNothing() 函数是为了设置void函数什么也不做

需要注意的是默认情况下返回值为void的函数在mocks中是什么也不做的但是,也会有一些特殊情况。如:

测试桩连续调用一个void函数时:

代码语言: javascript
复制
   doNothing().doThrow(new RuntimeException()).when(mock).someVoidMethod(); //does nothing the first time: mock.someVoidMethod(); //throws RuntimeException the next time: mock.someVoidMethod();

监控真实的对象并且你想让void函数什么也不做:

代码语言: javascript
复制
List list = new LinkedList(); List spy = spy(list); //let's make clear() do nothing doNothing().when(spy).clear(); spy.add("one"); //clear() does nothing, so the list still contains "one" spy.clear(); 
使用 doAnswer() 函数测试void函数的回调

当你想要测试一个无返回值的函数时,可以使用一个含有泛型类Answer参数的doAnswer()函数做回调测试。假设你有一个void方法有多个回调参数,当你想指定执行某个回调时,使用thenAnswer很难实现了,如果使用doAnswer()将非常简单,示例代码如下:

代码语言: javascript
复制
MyCallback callback = mock(MyCallback.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { //获取第一个参数 MyCallback call = invocation.getArgument(0); //指定回调执行操作 call.onSuccess(); return null; } }).when(mockedObject.requset(callback)); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { System.out.println("onSuccess answer"); return null; } }).when(callback).onSuccess(); mockedObject.requset(callback)
需要使用doReturn函数代替thenReturn的情况

如当监控真实的对象并且调用真实的函数带来的影响时

代码语言: javascript
复制
List list = new LinkedList(); List spy = spy(list); //不可能完成的:真实方法被调用的时候list仍是空的,所以spy.get(0)会抛出IndexOutOfBoundsException()异常 when(spy.get(0)).thenReturn("foo"); //这时你应该使用doReturn()函数 doReturn("foo").when(spy).get(0);
使用 doThrow() 函数来测试void函数抛出异常
代码语言: javascript
复制
SimpleClass mock = mock(SimpleClass.class); doThrow(new RuntimeException()).when(mock).someVoidMethod(); mock.someVoidMethod();

总之使用 doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 这些函数时可以在适当的情况下调用 when() 来解决一些问题., 如当你需要下面这些功能时这是必须的:

  • 测试void函数
  • 在受监控的对象上测试函数
  • 不只一次的测试同一个函数,在测试过程中改变mock对象的行为
4. 验证方法的调用次数

需要配合使用一些方法

方法

含义

times(int wantedNumberOfInvocations)

验证调用方法的次数

never()

验证交互没有发生,相当于times(0)

only()

验证方法只被调用一次,相当于times(1)

atLeast(int minNumberOfInvocations)

至少进行n次验证

atMost(int maxNumberOfInvocations)

至多进行n次验证

after(long millis)

在给定的时间后进行验证

timeout(long millis)

验证方法执行是否超时

description(String description)

验证失败时输出的内容

verifyZeroInteractions

验证mock对象没有交互

例如:

代码语言: javascript
复制
mock.someMethod("some arg"); mock.someMethod("some arg"); //验证mock.someMethod("some arg")被连续调用两次,即如果没有调用两次则验证失败 verify(mock, times(2)).someMethod("some arg");
代码语言: javascript
复制
//注意,下面三种是等价的,都是验证someMethod()被只调用一次 verify(mock).someMethod("some arg"); verify(mock, times(1)).someMethod("some arg"); verify(mock, only()).someMethod("some arg");
代码语言: javascript
复制
mPerson.getAge(); mPerson.getAge(); //验证至少调用2次 verify(mPerson, atLeast(2)).getAge(); //验证至多调用2次 verify(mPerson, atMost(2)).getAge();
代码语言: javascript
复制
//下面两种等价,验证调用次数为0 verify(mPerson, never()).getAge(); verify(mPerson, times(0)).getAge();
代码语言: javascript
复制
mPerson.getAge(); mPerson.getAge(); long current = System.currentTimeMillis(); System.out.println(current ); //延时1s后验证mPerson.getAge()是否被执行了2次 verify(mPerson, after(1000).times(2)).getAge(); System.out.println(System.currentTimeMillis() - current);
代码语言: javascript
复制
 mPerson.getAge(); mPerson.getAge(); //验证方法在100ms超时前被调用2次 verify(mPerson, timeout(100).times(2)).getAge();
代码语言: javascript
复制
  @Test public void testVerifyZeroInteractions() { Person person = mock(Person.class); person.eat("a"); //由于person对象发生了交互,所以这里验证失败,把上面的调用注释掉这里就会验证成功 verifyZeroInteractions(person); //可以验证多个对象没有交互 //verifyZeroInteractions(person,person2 ); }
代码语言: javascript
复制
  @Test public void testVerifyZeroInteractions() { Person person = mock(Person.class); person.eat("a"); verify(person).eat("a"); //注意,这将会无法到达验证目的,不能跟verify()混用 verifyZeroInteractions(person,person2 ); }
5. 参数匹配器 (matchers)

Mockito以自然的java风格来验证参数值: 使用equals()函数。有时,当需要额外的灵活性时你可能需要使用参数匹配器,也就是argument matchers :

代码语言: javascript
复制
// 使用内置的anyInt()参数匹配器 when(mockedList.get(anyInt())).thenReturn("element"); // 使用自定义的参数匹配器( 在isValid()函数中返回你自己的匹配器实现 ) when(mockedList.contains(argThat(isValid()))).thenReturn("element"); // 输出element System.out.println(mockedList.get(999)); // 你也可以验证参数匹配器 verify(mockedList).get(anyInt());

常用的参数匹配器:

方法名

含义

anyObject()

匹配任何对象

any(Class type)

与anyObject()一样

any()

与anyObject()一样

anyBoolean()

匹配任何boolean和非空Boolean

anyByte()

匹配任何byte和非空Byte

anyCollection()

匹配任何非空Collection

anyDouble()

匹配任何double和非空Double

anyFloat()

匹配任何float和非空Float

anyInt()

匹配任何int和非空Integer

anyList()

匹配任何非空List

anyLong()

匹配任何long和非空Long

anyMap()

匹配任何非空Map

anyString()

匹配任何非空String

contains(String substring)

参数包含给定的substring字符串

argThat(ArgumentMatcher matcher)

创建自定义的参数匹配模式

eq(T value)

匹配参数等于某个值

一些示例代码:

代码语言: javascript
复制
    @Test public void testPersonAny(){ when(mPerson.eat(any(String.class))).thenReturn("米饭"); //或: when(mPerson.eat(anyString())).thenReturn("米饭"); //输出米饭 System.out.println(mPerson.eat("面条")); } @Test public void testPersonContains(){ when(mPerson.eat(contains("面"))).thenReturn("面条"); //输出面条 System.out.println(mPerson.eat("面")); } @Test public void testPersonArgThat(){ //自定义输入字符长度为偶数时,输出面条。 when(mPerson.eat(argThat(new ArgumentMatcher<String>() { @Override public boolean matches(String argument) { return argument.length() % 2 == 0; } }))).thenReturn("面条"); //输出面条 System.out.println(mPerson.eat("1234")); }

需要注意的是,如果你打算使用参数匹配器,那么所有参数都必须由匹配器提供。例如:

代码语言: javascript
复制
verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); // 上述代码是正确的,因为eq()也是一个参数匹配器 verify(mock).someMethod(anyInt(), anyString(), "third argument"); // 上述代码是错误的, 因为所有参数必须由匹配器提供,而参数"third argument"并非由参数匹配器提供,因此会抛出异常

像anyObject(), eq()这样的匹配器函数不会返回匹配器。它们会在内部将匹配器记录到一个栈当中,并且返回一个假的值,通常为null。

6. 使用InOrder验证执行执行顺序

验证执行执行顺序主要使用 InOrder 函数 如,验证mock一个对象的函数执行顺序:

代码语言: javascript
复制
    @Test public void testInorder() { List<String> singleMock = mock(List.class); singleMock.add("小明"); singleMock.add("小红"); // 为该mock对象创建一个inOrder对象 InOrder inOrder = inOrder(singleMock); // 验证add函数首先执行的是add("小明"),然后才是add("小红"),否则测试失败 inOrder.verify(singleMock).add("小明"); inOrder.verify(singleMock).add("小红"); }

验证多个mock对象的函数执行顺序:

代码语言: javascript
复制
    @Test public void testInorderMulti() { List<String> firstMock = mock(List.class); List<String> secondMock = mock(List.class); firstMock.add("小明"); secondMock.add("小红"); // 为这两个Mock对象创建inOrder对象 InOrder inOrder = inOrder(firstMock, secondMock); // 验证它们的执行顺序 inOrder.verify(firstMock).add("小明"); inOrder.verify(secondMock).add("小红"); }

验证执行顺序是非常灵活的,你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可。 你可以选择单个mock对象和多个mock对象混合着来,也可以仅通过那些需要验证顺序的mock对象来创建InOrder对象。

7. 使用Spy监控真实对象

监控真实对象使用 spy() 函数生成,或者也可以像@Mock那样使用 @Spy 注解来生成一个监控对象, 当你你为真实对象创建一个监控(spy)对象后,在你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了。尽量少使用spy对象,使用时也需要小心形式。

代码语言: javascript
复制
    @Test public void testSpy() { List<String> list = new ArrayList<>(); List<String> spy = spy(list); // 你可以选择为某些函数打桩 when(spy.size()).thenReturn(100); // 调用真实对象的函数 spy.add("one"); spy.add("two"); // 输出第一个元素"one" System.out.println(spy.get(0)); // 因为size()函数被打桩了,因此这里返回的是100 System.out.println(spy.size()); // 验证交互 verify(spy).add("one"); verify(spy).add("two"); }

使用 @Spy 生成监控对象:

代码语言: javascript
复制
    @Spy
    Person mSpyPerson;
 
推荐文章
乖乖的芹菜  ·  粪土当年万户侯,万户侯是什么,侯又是什么?_主公
9 月前
温暖的凉茶  ·  背靠大树的赫罗纳,不做被看低的“小曼城”_米歇尔_城市_本赛季
1 年前
旅行中的荒野  ·  无颜之月讲的什么 – 快看漫画问答
1 年前
老实的刺猬  ·  读者主要看的是剧情还是文笔,还是都看? - 知乎
2 年前
买醉的饭盒  ·  十四届全国人大一次会议首场“部长通道” 王志刚部长回应关切 - 中华人民共和国科学技术部
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号