面向开发的测试技术(一):Mock

引子:自上世纪末Kent Beck提出TDD(Test-Driven Development)开发理念以来,开发和测试的边界变的愈来愈模糊,从本来上下游的依赖关系,逐步演变成你中有我、我中有你的互赖关系,甚至不少公司设立了新的QE(Quality Engineer)职位。和传统的QA(Quality Assurance)不一样,QE的主要职责是经过工程化的手段保证项目质量,这些手段包括但不只限于编写单元测试、集成测试,搭建自动化测试流程,设计性能测试等。能够说,QE身上兼具了QA的质量意识和开发的工程能力。从这篇开始,我会从开发的角度分三期聊聊QE这个亦测试亦开发的角色所需的基本技能。java

1 什么是Mock?

在软件测试领域,Mock的意思是模拟,简单来讲,就是经过某种技术手段模拟测试对象的行为,返回预先设计的结果。这里的关键词是预先设计,也就是说对于任意被测试的对象,能够根据具体测试场景的须要,返回特定的结果。打个比方,就像BBC纪录片里面的假企鹅,能够根据拍摄须要做出不一样的反应。git

2 Mock有什么用?

理解了什么是Mock,再来看Mock有哪些用途。首先,Mock能够用来解除测试对象对外部服务的依赖(好比数据库,第三方接口等),使得测试用例能够独立运行。不论是传统的单体应用,仍是如今流行的微服务,这点都特别重要,由于任何外部依赖的存在都会极大的限制测试用例的可迁移性和稳定性。可迁移性是指,若是要在一个新的测试环境中运行相同的测试用例,那么除了要保证测试对象自身可以正常运行,还要保证全部依赖的外部服务也可以被正常调用。稳定性是指,若是外部服务不可用,那么测试用例也可能会失败。经过Mock去除外部依赖以后,不论是测试用例的可迁移性仍是稳定性,都可以上一个台阶。github

Mock的第二个好处是替换外部服务调用,提高测试用例的运行速度。任何外部服务调用至少是跨进程级别的消耗,甚至是跨系统、跨网络的消耗,而Mock能够把消耗下降到进程内。好比原来一次秒级的网络请求,经过Mock能够降至毫秒级,整整3个数量级的差异。web

Mock的第三个好处是提高测试效率。这里说的测试效率有两层含义。第一层含义是单位时间运行的测试用例数,这是运行速度提高带来的直接好处。而第二层含义是一个QE单位时间建立的测试用例数。如何理解这第二层含义呢?以单体应用为例,随着业务复杂度的上升,为了运行一个测试用例可能须要准备不少测试数据,与此同时还要尽可能保证多个测试用例之间的测试数据互不干扰。为了作到这一点,QE每每须要花费大量的时间来维护一套可运行的测试数据。有了Mock以后,因为去除了测试用例之间共享的数据库依赖,QE就能够针对每个或者每一组测试用例设计一套独立的测试数据,从而很容易的作到不一样测试用例之间的数据隔离性。而对于微服务,因为一个微服务可能级联依赖不少其余的微服务,运行一个测试用例甚至须要跨系统准备一套测试数据,若是没有Mock,基本上能够说是不可能的。所以,不论是单体应用仍是微服务,有了Mock以后,QE就能够省去大量的准备测试数据的时间,专一于测试用例自己,天然也就提高了单人的测试效率。spring

3 如何Mock?

说了这么多Mock的好处,那么究竟如何在测试中使用Mock呢?针对不一样的测试场景,能够选择不一样的Mock框架。数据库

3.1 Mockito

若是测试对象是一个方法,尤为是涉及数据库操做的方法,那么Mockito多是最好的选择。做为使用最普遍的Mock框架,Mockito出于EasyMock而胜于EasyMock,乃至被默认集成进Spring Testing。其实现原理是,经过CGLib在运行时为每个被Mock的类或者对象动态生成一个代理对象,返回预先设计的结果。集成Mockito的基本步骤是:api

  1. 标记被Mock的类或者对象,生成代理对象
  2. 经过Mockito API定制代理对象的行为
  3. 调用代理对象的方法,得到预先设计的结果

下面是我GitHub上的示例工程里的一个例子,网络

@RunWith(SpringRunner.class)
@SpringBootTest
public class SignonServiceTests {

    // 测试对象,一个服务类
    @Autowired
    private SignonService signonService;

    // 被Mock的类,被服务类所依赖的一个DAO类
    @MockBean
    private SignonDao dao;

    @Test
    public void testFindAll() {
        // SignonService#findAll()内部会调用SignonDao#findAll()
        // 若是不作定制,全部被Mock的类默认返回空
        List<Signon> signons = signonService.findAll();
        assertTrue(CollectionUtils.isEmpty(signons));

        // 定制返回结果
        Signon signon = new Signon();
        signon.setUsername("foo");
        when(dao.findAll()).thenReturn(Lists.newArrayList(signon));

        signons = signonService.findAll();
        // 验证返回结果和预先设计的结果一致
        assertEquals(1, signons.size());
        assertEquals("foo", signons.get(0).getUsername());
    }
}复制代码

从上面的测试用例能够看到,经过Mock服务类所依赖的DAO类,咱们能够跳过全部的数据库操做,任意定制返回结果,从而专一于测试服务类内部的业务逻辑。这是传统的非Mock测试所难以实现的。app

注意:Mockito不支持Mock私有方法或者静态方法,若是要Mock这类方法,可使用PowerMock框架

3.2 WireMock

若是说Mocketo是瑞士军刀,能够Mock Everything,那么WireMock就是为微服务而生的倚天剑。和处在对象层的Mockito不一样,WireMock针对的是API。假设有两个微服务,Service-A和Service-B,Service-A里的一个API(姑且称为API-1),依赖于Service-B,那么使用传统的测试方法,测试API-1时必然须要同时启动Service-B。若是使用WireMock,那么就能够在Service-A端Mock全部依赖的Service-B的API,从而去掉Service-B这个外部依赖。

一样看一个我GitHub上的示例工程里的一个例子,

@RunWith(SpringRunner.class)
@WebMvcTest(VacationController.class)
public class VacationControllerTests {

    // Mock被依赖的另外一个微服务
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(3001);

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Before
    public void before() throws JsonProcessingException {
        // 定制返回结果
        JsonResult<Boolean> expected = JsonResult.ok(true);
        stubFor(get(urlPathEqualTo("/api/vacation/isWeekend"))
                .willReturn(aResponse()
                        .withStatus(OK.value())
                        .withHeader(CONTENT_TYPE, APPLICATION_JSON_UTF8_VALUE)
                        .withBody(objectMapper.writeValueAsString(expected))));
    }

    @Test
    public void testIsWeekendProxy() throws Exception {
        // 构造请求参数
        VacationRequest request = new VacationRequest();
        request.setType(PERSONAL);
        OffsetDateTime lastSunday = OffsetDateTime.now().with(TemporalAdjusters.previous(SUNDAY));
        request.setStart(lastSunday);
        request.setEnd(lastSunday.plusDays(1));

        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/vacation/isWeekend");
        request.toMap().forEach((k, v) -> builder.param(k, v));
        JsonResult<Boolean> expected = JsonResult.ok(true);

        mockMvc.perform(builder)
                // 验证返回结果和预先设计的结果一致
                .andExpect(status().isOk())
                .andExpect(content().contentType(APPLICATION_JSON_UTF8))
                .andExpect(content().string(objectMapper.writeValueAsString(expected)));
    }
}复制代码

和Mockito相似,在测试用例中集成WireMock的基本步骤是:

  1. 声明代理服务,以替代被Mock的微服务
  2. 经过WireMock API定制代理服务的返回结果
  3. 调用代理服务,得到预先设计的结果

值得一提的是,除了API方式的集成,WireMock还支持以Jar包的形式独立运行,从配置文件中加载预先设计的响应结果,以替代被Mock的微服务。更多信息能够参阅官方文档

其余相似的Mock API的框架还有OkHttp的mockwebservermocomockserver。mockwebserver也属于嵌入式Mock框架的范畴,但功能过于简单。moco,mockserver虽然功能完善,但须要独立部署,和WireMock相比不具备优点。

4 小结

以上就是我对Mock技术的一些看法,欢迎你到个人留言板分享,和你们一块儿过过招。最后还要说一句,Mock技术虽然强大,但主要仍是适用于单元测试,在集成测试,性能测试,自动化测试等其余测试领域使用并很少。

5 参考

相关文章
相关标签/搜索