不是干货我叫你爸爸(三):入门Mock技术

 

不是干货我叫你爸爸(三):入门Mock技术

Hello,everyone,我是搜狗华安...



Hello,everyone,我是搜狗华安,代号9527。又是周四,很高兴又和大家见面了。这次又给大家带来成长快乐。



好了,来个开场白,咱们直接看今天的大补,服务端测试之MOCK技术。

自上世纪末Kent Beck提出TDD(Test-Driven Development)开发理念以来,开发和测试的边界变的越来越模糊,从原本上下游的依赖关系,逐步演变成你中有我、我中有你的互赖关系,新的测试人员(QA,QE,SDET等)的主要职责是通过工程化的手段保证项目质量,这些手段包括但不仅限于编写单元测试、集成测试,搭建自动化测试流程,设计性能测试等。可以说,在新的互联网日常项目中,对于测试人员的技术实力要求越来越严格,要求具备质量意识和开发的工程能力。从这篇开始,我会从开发的角度分多期俩聊聊这个亦测试亦开发的角色所需的基本技能,可以持续关注《不干叫爸》系列。

1 什么是Mock

截取一段stackflow中的解释:

Mocking isprimarily used in unit testing. An object under test may have dependencies onother (complex) objects. To isolate the behaviour of the object you want totest you replace the other objects by mocks that simulate the behavior of thereal objects. This is useful if the real objects are impractical to incorporateinto the unit test.

MOCK主要被用于在单测中,某个对象在测试过程中有可能依赖于其他的复杂对象,通过mocks去模拟真实的其他对象(模块)去代替你想要去测试的其他的对象(模块),如果其他对象(模块)是很难从单元测试中剥离开来的话,这是非常有用的…..人工翻译,大概意思ok。准确翻译请使用采用人工智能翻译的fanyi.sogou.com :) 实力硬广。

2 Mock有什么用?

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



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

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

3 如何Mock

说了这么多Mock的好处,那么究竟如何在测试中使用Mock呢?针对不同的测试场景,可以选择不同的Mock框架。

3.1 Mockito

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

1.   标记被Mock的类或者对象,生成代理对象

2.   通过Mockito API定制代理对象的行为

3.   调用代理对象的方法,获得预先设计的结果

下面是我网上随手找的一个例子,

@RunWith(SpringRunner.class)

@SpringBootTest

publicclassSignonServiceTests {

// 测试对象,一个服务类

@Autowired

private SignonService signonService;

// 被Mock的类,被服务类所依赖的一个DAO类

@MockBean

private SignonDao dao;

@Test

publicvoidtestFindAll() {

// SignonService#findAll()内部会调用SignonDao#findAll()

// 如果不做定制,所有被Mock的类默认返回空

List signons = signonService.findAll();

assertTrue(CollectionUtils.isEmpty(signons));

// 定制返回结果

Signonsignon = 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测试所难以实现的。

注意: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-AMock所有依赖的Service-B的API,从而去掉Service-B这个外部依赖。

同样看一个别人的一个例子:

@RunWith(SpringRunner.class)

@WebMvcTest(VacationController.class)

publicclassVacationControllerTests {

// Mock被依赖的另一个微服务

@Rule

public WireMockRule wireMockRule = new WireMockRule(3001);

@Autowired

private MockMvc mockMvc;

@Autowired

private ObjectMapper objectMapper;

@Before

publicvoidbefore() throwsJsonProcessingException {

// 定制返回结果

JsonResult 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

publicvoidtestIsWeekendProxy() 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 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的mockwebserver,moco和mockserver。mockwebserver也属于嵌入式Mock框架的范畴,但功能过于简单。moco,mockserver虽然功能完善,但需要独立部署,和WireMock相比不具有优势。

4 小结

好啦,本期分享才不多到此,以上就是我对Mock技术的一些见解,欢迎留言分享你的意见和建议,或者识别以下二维码,加入qq群:459645679。最后还要说一句,Mock技术虽然强大,但主要还是适用于单元测试,在集成测试,性能测试,自动化测试等其他测试领域使用并不多。使用时候更多是单个功能的验证。期待下次和你见面,我是搜狗华安,代号9527


    关注 搜狗测试


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册