SpringBoot中单元测试原理

1. Mock 的基本介绍

1.1 mock的定义:

mock是在测试过程中,对于一些不容易构造/获取的对象,创建一个mock对象来模拟对象的行为。

1.2 为什么要使用mock

在做单元测试过程中,经常会有以下的场景:

class A 依赖 class B
class B 依赖 class C和class D
class C 依赖 ...
class D 依赖 ...

  • 1.被测对象依赖的对象构造复杂
    我们想对class A进行单元测试,需要构造大量的class B、C、D等依赖对象,他们的构造过程复杂(体现在构造步骤多、耗时较长),这时我们可以利用mock去构造虚拟的class B、C、D对象用于class A的测试,因为我们只是想测试class A的行为是否符合预期,我们并不需要测试依赖对象。

  • 2.被测单元依赖的模块尚未开发完成,而被测对象需要依赖模块的返回值进行测试:
    ----- 比如service层的代码中,包含对dao层的调用,但dao层代码尚未开发
    ----- 比如web的前端依赖后端接口获取数据进行联调测试,但后端接口并未开发完成

被测对象和依赖对象关系图

1.3 哪些时机和场合需要使用mock

  • 1.单元测试/接口测试中测试对象依赖其他对象,这些对象的构造复杂、耗时或者根本无法构造(未交付)
  • 2.我们只测试对象内部逻辑的质量,不关心依赖对象的逻辑正确性和稳定性

1.4 是否应该在测试中使用mock,投入产出比如何

我们可以基于以下2个原则去做mock,这样的mock的投入产出比是最高的

  • 1.不需要对所有的依赖对象/服务进行mock,只对那些构造步骤复杂、构造耗时较长、不稳定的依赖对象/服务进行mock。

  • 2.如果做分层测试(比如分层自动化),高层的测试设计可以基于以下假设:低层的测试已保证低层对象的质量,高层对低层的依赖可以mock,无需关心所依赖的低层对象的质量。

1.5 Local Mock和Remote Mock

Local Mock和Remote Mock的区别在于依赖对象的行为调用是本地/远程调用

  • 1.Mock本地依赖对象
    如果依赖对象的调用是本地调用,就属于Local Mock。EasyMock/Mockito都可以实现对本地依赖对象的Mock。

  • 2.Remote Mock
    如果被测对象依赖对象的是一些远程服务(HTTP/RPC服务),就属于Remote Mock,Remote Mock一般实现对指定的服务/接口客户端(消费者)进行Mock。可以通过Mock Server等方式去实现。

Local Mock 和 Remote Mock

LocalMock相当于使用Mock对象替换依赖对象。
RemoteMock相当于使用MockServer替换真实的HTTP/RPC Server。

2. Mock工具

1.Mockito
Mockito参考

2.EasyMock
EasyMock参考

3.PowerMock
PowerMock官网

3. Mockito 的使用

Mockito 是一个流行的 mock 框架,可以与 JUnit 结合使用,Mockito 允许我们创建和配置 mock 对象,使用 Mockito 将大大简化了具有外部依赖项的类的测试开发。spring-boot-starter-test 中默认集成了 Mockito,不需要额外引入。
在测试中使用 Mockito,通常会:

  • mock 外部依赖关系并将 mock 对象插入待测代码

  • 执行被测代码

  • 验证代码是否正确执行


3.1 使用 Mockito 创建 mock 对象

Mockito提供了几种创建 mock 对象的方法:

  • 使用静态 mock() 方法

  • 使用 @Mock 注解

对于 @Mock,@Spy, @InjectMocks等注解的成员变量初始化到目前为止有2种方法:

  • 对 JUnit 测试类添加 @RunWith(MockitoJUnitRunner.class)
  • @Before方法内调用初始化方法:MockitoAnnotations.initMocks(Object)

现在,正如我们上面的测试用例所示,对于@Mock等注解的成员变量的初始化又多了一种方法:MockitoRule
规则MockitoRule会自动帮我们调用MockitoAnnotations.initMocks(this)去实例化出注解的成员变量,我们就无需手动进行初始化了。

public class UnitTest3 {

    // 触发创建带有 @Mock 注解的对象
    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
    // 1. 使用 @Mock 注解创建 mock 对象
    @Mock private UnitDao unitDao;

    @Test
    public void test() {
        // 2. 使用静态 mock() 方法创建 mock 对象
        Iterator iterator = mock(Iterator.class);
        // when...thenReturn / doReturn...when 模拟依赖调用
        when(iterator.next()).thenReturn("hello");
        doReturn(1).when(unitDao).delete(anyLong());
        // 断言
        Assert.assertEquals("hello", iterator.next());
        Assert.assertEquals(new Integer(1), unitDao.delete(1L));
    }
}

3.2 使用 mock 对象实践单元测试

我们要单元测试的内容,常常包含着对数据库的访问等等,那么我们要如何 mock 掉这部分调用呢?我们可以使用 @InjectMocks 注解创建实例并使用 mock 对象进行依赖注入。

@Service
public class UnitServiceImpl implements UnitService {

    @Autowired
    private UnitDao unitDao;

    @Override
    public String sayHello() {
        Integer delete = unitDao.delete(1L);
        System.out.println(delete);
        return "hello unit";
    }
}

@RunWith(MockitoJUnitRunner.class)
public class UnitTest2 {
    
    @Mock
    private UnitDao unitDao;
    @InjectMocks
    private UnitServiceImpl unitService;

    @Test
    public void unitTest() {
        // mock 调用
        when(unitDao.delete(anyLong())).thenReturn(1);
        Assert.assertEquals("hello unit", unitService.sayHello());
    }
}

3.3 使用 PowerMock mock 静态方法。

Mockito 也有一些局限性。例如:不能 mock 静态方法和私有方法。这个时候我们就要用到 PowerMock,PowerMock 支持 JUnit 和 TestNG,扩展了 EasyMock 和 Mockito 框架,增加了mock static、final 方法的功能。

首先需要引入 PowerMock 的依赖:

      <!-- PowerMock -->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.7</version>
        </dependency>

接下来就能愉快的 mock 静态方法了。

@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class UnitTest4 {

    @Test
    public void test() {
        mockStatic(StringUtils.class);
        when(StringUtils.getFilename(anyString())).thenReturn("localhost");
        Assert.assertEquals("localhost", StringUtils.getFilename(""));
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容