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

前言

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

关于单元测试的命名

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

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

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


使用mockito

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

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

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

导入依赖

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

在这里插入图片描述
版本选择稳定最新的即可


图文讲解打桩–关于打桩
在这里插入图片描述
虽然mockedList是模仿出来的,但是我们设置的打桩以及返回值.那么他就会返回对应的期望值
在这里插入图片描述
如果输出一个我没有打桩的下标
在这里插入图片描述

返回结果为 null

在这里插入图片描述


参数匹配器

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


在这里插入图片描述
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行为验证

在这里插入图片描述
模拟出了一个List,List添加了一个元素为 “ 1 ” 验证是否增加了元素 ‘ 1 ’

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


验证行为发生的次数

在这里插入图片描述


验证对象行为

说明

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

示例

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

在这里插入图片描述


验证行为发生的

说明

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

    @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");
    }

在这里插入图片描述

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);

    }
}

在这里插入图片描述
示例解析

执行 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)

总结


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

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

在这里插入图片描述

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

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

先基本后分支

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

先正常后异常

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值