单元测试 -- mock模拟测试总结

前言

世面上测试的工具还是比较多的。本人用mock模拟测试用的比较多。所以一种用好了,其实都是大同小异。可以自测就可以了

关于单元测试的命名

测试类的命名规范:被测试类的类名+Test后缀。

测试用例的命名:由于一个被测试单元可能对应着不同的场景,因此测试用例应根据场景命名。比如某测试用例的场景为“当参数不合法时抛出异常”,则可以命名为
“throwExceptionBy异常名称”.

变量命名:测试一个单元时,预期的结果命名expectedResult,实际结果命名为actualResult 所有预期的都以expected为前缀


使用mockito

所谓的mock就是创建一个类的模拟的对象,在测试环境中,用来替换掉真实的对象,以达到两个目的

  • 1.验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等

  • 2.指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作

导入依赖

MVN仓库连接: http://mvnrepository.com/artifact/org.mockito/mockito-a.

单元测试 -- mock模拟测试总结_第1张图片
版本选择稳定最新的即可


图文讲解打桩–关于打桩
单元测试 -- mock模拟测试总结_第2张图片
虽然mockedList是模仿出来的,但是我们设置的打桩以及返回值.那么他就会返回对应的期望值
单元测试 -- mock模拟测试总结_第3张图片
如果输出一个我没有打桩的下标
单元测试 -- mock模拟测试总结_第4张图片

返回结果为 null

在这里插入图片描述


参数匹配器

简单说下为何要用参数匹配器,如果参数比较多,我不可能一个个去给他设定,我需要批量设定,比如 get(1到100)的参数都打桩返回 “first” 在这种情况下,就需要使用参数匹配器

单元测试 -- mock模拟测试总结_第5张图片
单元测试 -- mock模拟测试总结_第6张图片
any匹配所有对应的类型

常用的匹配字符列表

anyBoolean,

anyByte,

anyChar,

anyCollection,

anyCollectionOf,

anyDouble,

anyFloat,

anyInt, anyList,

anyListOf, anyLong,

anyMap, anyMapOf,

anyObject,

anySet,

anySetOf,

anyShort,

anyString,

anyVararg.


any匹配参数自定义

Mockito 中,参数常常是自定义的比如是某 个 DTO 等 。 可 以 使 用 any(DTO.class) 比如 anyInt() 也 可 以 写 成 any(Integer.class),效果等同.


verify行为验证

单元测试 -- mock模拟测试总结_第7张图片
模拟出了一个List,List添加了一个元素为 “ 1 ” 验证是否增加了元素 ‘ 1 ’

如果确认成功那么测试通过,否则报错。


验证行为发生的次数

单元测试 -- mock模拟测试总结_第8张图片


验证对象行为

说明

对于一个 mock 出来的对象,在代码执行结束后,需要验证其相关行为是否发生。被 mock的对象一旦创建,Mock 框架将会记录其所有行为,我们可以有针对性地选择一些进行验证。

示例

示例解析 当前示例中,我们 Mock 一个实例 mockedList,后两句的 verify 验证 mockedList
是否执行了 add 和 clear 两个行为。verify 用于验证对象的行为;

单元测试 -- mock模拟测试总结_第9张图片


验证行为发生的

说明

一个对象的某个行为在一段执行逻辑中,有可能会发生多次,在验证时需要对其执行次数进行验证。

    @Test
    public void testDemoOne() {
        List mockedList = mock(List.class);
        mockedList.add("once");
        mockedList.add("twice");
        mockedList.add("twice");
        mockedList.add("three times");
        mockedList.add("three times");
        mockedList.add("three times");
//下面这两种使用方法效果是一样的。默认是 times(1)
        Mockito.verify(mockedList).add("once");
        Mockito.verify(mockedList, times(1)).add("once");
//次数验证 是否添加两次,是否添加三次
        Mockito.verify(mockedList, times(2)).add("twice");
        Mockito.verify(mockedList, times(3)).add("three times");
//使用 never(). never() 是 times(0)的别名,两者效果等同
        Mockito.verify(mockedList, never()).add("never happened");
//使用 atLeast()
        Mockito.verify(mockedList, atLeastOnce()).add("three times");
    }

单元测试 -- mock模拟测试总结_第10张图片

times(1)是默认的,所以使用时可以省略


测试预期异常

说明

测试代码中期望的异常抛出

ublic class DemoOneTest {



    // 允许抛出异常初始化
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void vote_throws_IllegalArgumentException_for_zero_age() {
        Student student = new Student();
//期望的异常类型为 IllegalArgumentException
        thrown.expect(IllegalArgumentException.class);
//期望的异常信息:age should be +ve
        thrown.expectMessage("age should be +ve");
        student.vote(0);

    }
}

单元测试 -- mock模拟测试总结_第11张图片
示例解析

执行 student.vote(0)时,如果确实有期望的异常,则测试通过。否则,测试失败


为了防止图片不完整,我用代码实例

用@Mock 简化 mock 对象的

说明

在此前的例子,mock 一个对象时,用的是 mock(List.class)等,本节将描述如何简化
创建,这将使我们的代码更简洁、更易读。

//等同于 ArticleCalculator calculator=mock(ArticleCalculator.clss);
@Mock
private ArticleCalculator calculator;
@Mock
private ArticleDatabase database;
@Mock
private UserProvider userProvider;
private ArticleManager manager;

示例解析

需要注意的是,使用该方法创建 mock 对象时,需要在测试用例执行前执行以下代码。通常,这句代码可以放在测试基类或者@Before 中。

MockitoAnnotations.initMocks(this); 

意思为初始化Mock环境,如果使用@Mock注解需要在@Test之前初始化MOCK,如果涉及的序列码。请使用

  @BeforeClass
    public void initMock() {
        MockitoAnnotations.initMocks(this);
    }

否者可以使用

 @Rule
    public PowerMockRule rule = new PowerMockRule();

同样实现初始化代码;

用@spy 模拟真实对象的部分


说明

在某些情况下,我们需要使用一个真实对象。但是,我们同时需要自定义该对象的部分行为,此时用 @spy 就可以帮我们达到这个目的。

//通过 spy 创建一个可以模拟部分行为的对象,spy()接收的参数是一个真实对象
List list = spy(new LinkedList());
list.add(1);
when(list.get(0)).thenThrow(new Exception());
//如果 get(0)正常执行的话,将会返回 1.但是由于对象经过 spy 并打桩后,它
将抛出异常
list.get(0)//与 spy 相反的是,也可以让被 mock 的对象执行真实的行为
Foo mock = mock(Foo.class);
//Be sure the real implementation is 'safe'.
//If real implementation throws exceptions or depends on specific 
state of the object then you're in trouble.
when(mock.someMethod()).thenCallRealMethod();

示例解析

使用 thenCallRealMethod 时,需要注意真实的实现部分是安全的,否则将会带来麻烦

注意 Mock 和 spy 用法的区别在于:当测试用例中需要使用某个对象的真实方法更多些时,请使用 spy,反之请使用 Mock.

用@InjectMocks 完成依赖注入


说明

类中需要第三方协作者时,通常会用到 get 和 set 方法注入。通过 spring 框架也可以同 @Autowird 等方式完成自动注入。在单元测试中,没有启动 spring 框架,此时就需要通过 InjectMocks 完成依赖注入。InjectMocks 会将带有@Spy 和@Mock 注解的对象尝试注入到被测试的目标类中。

示例

public class ArticleManagerTest extends SampleBaseTestCase {
 @Mock 
 private ArticleCalculator calculator;
 @Mock(name = "database") 
 private ArticleDatabase dbMock; 
 @Spy 
 private UserProvider userProvider = new ConsumerUserProvider();
 @InjectMocks 
 private ArticleManager manager;
 @Test public void shouldDoSomething() {
 manager.initiateArticle();
 verify(database).addListener(any(ArticleListener.class));
 }
}

示例解析

InjectMocks 在注入依赖的对象前,会首先创建一个目标对象的实例。所以,manager 有@ InjectMocks 后,不需要再手动创建实例;

静态模拟方法


说明

与普通方法不同的是,静态方法必须通过其类直接调用。如此,Mockito 中的方法将不再适用于静态方法。此时需要使用 PowerMock.

PowerMock 是一个扩展了其它如 EasyMock 等 mock 框架的、功能更加强大的框架。PowerMock

使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final 类和方法,私有方法,去除静态初始化器等等。通过使用自定义的类加载器,简化采用的 IDE 或持续集成服务器不需要做任何改变。熟悉 PowerMock 支持的 mock 框架的开发人员会发现 PowerMock 很容易使用,因为对于静态方法和构造器来说,整个的期望 API 是一样的。PowerMock 旨在用少量的方法和注解扩展现有的 API 来实现额外的功能。目前 PowerMock 支持 EasyMock 和 Mockito。

引入PowerMock 依赖

<powermock-api-mockito.version>1.6.2</powermock-api-mockito.version>
<powermock-module-junit4.version>1.6.2</powermock-module-junit4.version>
<powermock-core.version>1.6.2</powermock-core.version>
<dependency>
 <groupId>org.powermock</groupId>
 <artifactId>powermock-api-mockito</artifactId>
 <version>${powermock-api-mockito.version}</version>
 <scope>test</scope>
 </dependency>
<dependency>
 <groupId>org.powermock</groupId>
 <artifactId>powermock-module-junit4</artifactId>
 <version>${powermock-module-junit4.version}</version>
 <scope>test</scope>
</dependency>
<dependency>
 <groupId>org.powermock</groupId>
 <artifactId>powermock-core</artifactId>
 <version>${powermock-core.version}</version>
 <scope>test</scope>
</dependency

示例

//带模拟的静态类
public class FileHelper {
 public static boolean isExist() {
 return false;
 }
}

声明使用powerMock


@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
 @Test
 @PrepareForTest(FileHelper.class)
 public void testCallStaticMethod() {
 ClassUnderTest underTest = new ClassUnderTest();
 PowerMockito.mockStatic(FileHelper.class);
 PowerMockito.when(FileHelper.isExist()).thenReturn(true);
 Assert.assertTrue(underTest.callStaticMethod());
verifyStatic();
FileHelper.isExist()
 }
}

示例解析

PowerMockRunner 在模拟静态时配置较多,使用时需要注意。另外,静态方法断言时,必须要有 verifyStatic(),然后需要断言的方法紧随其后,如上

Mock 使用时的注意事项


若单个测试用例中(限领域对象)出现了三个及以上的 Mock 对象时,表明被测试的单元与第三方发生了较多的交互,可能违反了单一职责原则。此时,你可能需要暂停测试用例的编写,检查代码设计是否存在设计问题,以保证业务代码和测试用例的可读性。测试用例的编写与重构紧密结合。代码的质量直接影响测试用例的编写,而代码质量的提高依赖于设计和重构。如果发现测试用例编写比较困难,你可能需要重新审视代码设计是否合理

断言


assertEquals

说明 比较两个对象的原始值。如果对象是值类型时,将调用对象的 equals().

示例

assertEqual(expectedResult,actualResult);

assertTrue

说明 验证结果是否为 true.

assertTrue(actualResult);

assertFalse

说明 验证结果是否为 false.

assertFalse(actualResult);

assertNotNull

说明 验证一个结果是否不为 null

assertNotNull(actualResult)

assertNull

说明 验证一个结果是否为 null.

assertNull(actualResul

assertSame

说明 比较两个对象的引用地址是否相同。

assertSame(expectedResult,actualResult)

总结


总原则:先基本后分支,先正常后异常。

用例场景要通过描述流经用例的路径来确定,这个流经过程要从用例开始到结束遍历其中所有
基本流和分支流。由此会产生很多组场景,如下图所示

单元测试 -- mock模拟测试总结_第12张图片

基本流:经过测试用例最简单的路

分支流:一个分支流可能从基本流开始,在某个特定条件下执行,然后重新加入基本流中(如分支流 1 和 3);也可能起源于另一个分支流(如分支流 2),或者终止用例而不再重新加入到某个流(如分支流 2 和异常流)

先基本后分支

基本流是业务的基本逻辑,也是业务逻辑的主线。先从基本流入手,一方面有助于抓住业务主线,另一方面在不考虑分支的情况下,先编写基本分支的测试用例,有助于测试用例的逐步深入,保持清晰的层次逻辑。

先正常后异常

先编写正常(正面)的测试用例,即在不发生异常情况下的业务逻辑。正面用例覆盖完全后,再编写异常(负面)的测试用例,用于测试程序对异常的处理情况。正常的测试用例包括正常的基本流和分支流,程序中的异常通常包

  • 参数校验错误;
  • 数据不符合正常的业务要求;
  • 边界错误;
  • 数据库操作失败;
  • 第三方系统调用异常

正常个人只是为了上线覆盖率,先正常无错误逻辑过一遍,使用powermock进行Dao层的访问是否返回成功。在进行错误的异常抛出,或者if之后的else分支再写一个test。
最后进行断言测试,有时候也不写。毕竟写这玩意费事···

你可能感兴趣的:(mock,单元测试,模拟测试)