公司要求提升单元测试的质量,提高代码的分支覆盖率和行覆盖率,安排我研究单元测试,指定方案分享并在开发部普及开。整理完资料后,同步一下到博客。

单元测试中的mock的目的

mock的主要目的是让单元测试Write Once, Run Everywhere,即编写一次后,可以在任意时刻任意环境运行,无需依赖数据库网络等。

Mock工具介绍

Mock工具经过调研,基本上是表格下面的这么个情况:

mockserver方案 开源 支持随机参数 支持请求延时模拟 支持参数上下文 仓库分组 接口管理 仪表盘 日志 支持管理台配置 支持编程
rap2,easy-mock等
wiremock
mock-server
postman

简要介绍下各个的特点和为什么没选:

  1. rap2和easy-mock等,都是基于node开发的,和我们开发部的主力语言Java相性一般,后续改造难度大,并且不支持请求超时的配置和上下文的配置,优点是使用操作简单,pass。
  2. wiremock,和rap2差不多,就是多个支持延时请求,不过是英文的,pass
  3. mock-server,基于java语言的,底层是netty,编程自由,比较适合java技术栈的团队。
  4. postman,虽然有mock功能,但是只能针对某个请求的返回固定mock,并且每次启动mock的端口和url完全随机,无法接受,pass。

我们最后选的是mockito和mock-server,mockito因为是java的mock工具包,所以并不在上面的表格里。

mockito

相关介绍:

这个包是spring官方也推荐的mock依赖,在spring-boot-starter-test中默认就会自动包含。

       这个包提供的相关类,主要功能就是对某个对象进行mock,通过其提供的特殊的语法,对某个对象的返回以及行为做mock。

应用场景:

单元测试时,如果依赖其他系统的RPC调用(比如feign或dubbo),可以针对相关RPC的调用对象进行直接mock,直接返回成功、超时、异常,减少依赖。

       在对系统内部的某些工具类或者数据库层进行单元测试时,可以模拟一些异常情况,比如数据库超时、框架层抛出某些很难复现的特定异常返回,可以通过直接mock实现来达到效果。

       mockito除了mock外也支持spy,mock与spy的区别是,mock产生的是一个空对象,对mock对象未做配置的方法调用均返回null或异常。spy产生的是一个代理对象,对那些做了配置的方法按照配置的预期返回,未做配置的方法直接会调用原方法。

使用方式(spring):

  1. maven中引入:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
  1. 在测试类中进行如下定义:
//需要mock的服务,一般是RPC,也可以是工具类,总而言之是一个对象
@Mock
TestRpc testRpc;
@Autowired
TestService testService; //在@Before中对其进行初始化
@Before
public void initMocks() throws Exception {
//1.1 初始化的api,在这一步执行后,testRpc被初始化为一个mock对象
MockitoAnnotations.initMocks(this);
//1.2 使用mock对象替换spring中的bean:这里是将后面要用到的testService中的testRpc这个rpc对象,
//替换为上面@Mock为我们创建的mock对象,然后我们就可以对这个对象进行mock了,这里的替换是spring容器级别的替换
//注意,理论上对RPC的service进行mock即可,即替换调用RPC的那个bean中的rpc对象。
ReflectionTestUtils.setField(AopTargetUtils.getTarget(orderPayFacade), "testRpc", testRpc);
//1.3 定义mock返回:对新的mock对象进行定义,当后续请求这个rpc的该方法时,会直接return一个空的成功对象
final ResultRpc<TestVO> testVo = new ResultRpc<>();
when(testRpc.getAccountByBindCardId("101010")).thenReturn(testVo);
}

或者

//另一种初始化方式,更加简单快捷
//这里是另一种写法,设置一个默认的answer,不用每个方法都设置一次返回,也可以继续进行上面那种方式的when配置
final TestRpc testRpcMock = mock(TestRpc.class, new Answer<TestRes>() {
@Override
public TestRes answer(InvocationOnMock invocationOnMock) throws Throwable {
final TestRes testRes = new TestRes();
testRes.setConfigId(0L);
testRes.setCityId(86);
testRes.setServiceId("01");
testRes.setSysJoinType(0);
testRes.setMerchantId("320212018002");
testRes.setMerchantCode("");
return testRes;
}
});
ReflectionTestUtils.setField(AopTargetUtils.getTarget(testService), "testRpc", testRpcMock);
  1. 然后直接正常执行测试即可。

使用方式(spring-boot及以上):

前面说了spring-boot-starter较高版本(2.0以上)的test中默认会包括该依赖,所以直接使用就行,更方便的是无需使用反射工具替换spring上下文的bean,使用@MockBean注解标识bean即可。

mock-server

相关资料:

官方文档https://www.mock-server.com/

应用场景:

当进行单元测试时,如果我们需要进行http请求级别的模拟以及mock,那么我们就可以使用mockserver

       当然mockito也可以通过直接mock那些http请求的类来达到相似效果,不过使用mock-server,我们可以更逼真的模拟http的环境,以提前发现那些只有在使用网络下才会出现的问题。

       既可以集成在maven的test生命周期里,也可以直接单独启动做一个server。

使用方式:

  1. maven中引入:
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>5.11.1</version>
</dependency>
  1. 在测试类中进行如下定义:
private final int mockPort = 19999;
private ClientAndServer mockServer;
//在@Before中对其进行初始化
@Before
public void initMocks() throws Exception {
//1.1 初始化的api:启动mockserver
mockServer = startClientAndServer(mockPort);
//1.2 配置mockServer
mockServer
.when(
request()
.withMethod("POST")
.withPath("/test/pay_v1/trade/pay")
.withContentType(MediaType.APPLICATION_JSON)
)
.respond(
new TestResponseCallBack()
);
}
public static class TestResponseCallBack implements ExpectationResponseCallback{
private final Gson gson=new Gson();
@Override
public HttpResponse handle(HttpRequest httpRequest) throws Exception {
log.info("------------{}",httpRequest);
if (httpRequest.getMethod().getValue().equals("POST")) {
//校验签名
boolean verify = doVerifySign(httpRequest);
if (!verify){
return response()
.withStatusCode(OK_200.code())
.withBody(gson.toJson(CommonResult.failure(CommonErrors.SIGNATURE_VERIFY_FAIL)));
}
//构造返回
return createResponse(httpRequest);
} else {
return notFoundResponse();
}
}
private HttpResponse createResponse(HttpRequest httpRequest) throws Exception {
final HttpRequest httpRequest1 = httpRequest;
final String req = new String(httpRequest.getBodyAsRawBytes());
String respBody="";
final JSONObject jsonObject= JSON.parseObject(req);
//比如对参数做一些校验
Assert.assertNotNull(jsonObject.getString("user_id"));
//构造返回,可以根据请求的内容构造,这里随便写个返回,
final String user_id = jsonObject.getString("user_id");
respBody="{\"success\": true,\"errcode\": \"0000\",\"errmsg\": \"成功\",\"result\": {\"user_id\": \"123456\",\"reserved\":"+user_id+"\"\"}}";
//这里如果必要的话,也可以触发一个延时的回调
new Thread(new Runnable() {
@Override
public void run() {
LockSupport.parkNanos(1000000000L*2);
final String notify_url = jsonObject.getString("notify_url");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
JSONObject param = new JSONObject();
param.put("username", "123");
HttpEntity<String> formEntity = new HttpEntity<String>(param.toJSONString(), headers);
String result = new RestTemplate().postForObject(notify_url, formEntity, String.class);
log.info("发送回调:{}",param.toJSONString());
}
}).start();
return response()
.withStatusCode(OK_200.code())
.withBody(respBody);
}
private boolean doVerifySign(HttpRequest httpRequest) throws Exception {
String signature = httpRequest.getFirstHeader(RequestHeader.Signature);
String message = new String(httpRequest.getBodyAsRawBytes(), StandardCharsets.UTF_8);
String md5HexMessage = DigestUtils.md5Hex(message.getBytes(StandardCharsets.UTF_8));
return RSAUtils.doCheck(md5HexMessage, signature, privateKey, StandardCharsets.UTF_8.displayName());
}
}
  1. 然后直接正常执行测试即可。

cobertura-maven-plugin

前面的2个mock工具,结合cobertura-maven-plugin,可以瞬间跑起一个带代码覆盖率的测试。

使用方式:

  1. maven
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
</plugin>
  1. 执行测试:mvn clean cobertura:cobertura -f pom.xml
  2. 到target/site下打开index文件查看结果:

总结

本人简单介绍了3个工具的使用,主要是提供了一个可行的方案去推进单元测试,具体3个工具的详细使用细节以及进阶,可以自行查找资料。

单元测试:单元测试中的mock的更多相关文章

  1. 单元测试Mockito中的Mock和Spy

    转载:https://blog.csdn.net/qq_30141957/article/details/81273829 项目中,有些函数需要处理某个服务的返回结果,而在对函数单元测试的时候,又不能 ...

  2. 单元测试系列之五:Mock工具之Mockito实战

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6780719.html 在实际项目中写单 ...

  3. 利用Python中的mock库对Python代码进行模拟测试

    这篇文章主要介绍了利用Python中的mock库对Python代码进行模拟测试,mock库自从Python3.3依赖成为了Python的内置库,本文也等于介绍了该库的用法,需要的朋友可以参考下     ...

  4. 【转】利用Python中的mock库对Python代码进行模拟测试

    出处 https://www.toptal.com/python/an-introduction-to-mocking-in-python http://www.oschina.net/transla ...

  5. python中使用mock模块返回数据

    mock是辅助单元测试的一个模块.它允许您用模拟对象替换您的系统的部分,并对它们已使用的方式进行断言. mock在python3中已经被集成到了unittest单元测试框架中,所以,可以直接使用. m ...

  6. Electron-vue实战(三)— 如何在Vuex中管理Mock数据

    Electron-vue实战(三)— 如何在Vuex中管理Mock数据 作者:狐狸家的鱼 本文链接:Vuex管理Mock数据 GitHub:sueRimn 在vuex中管理mock数据 关于vuex的 ...

  7. 比POSTMAN更好用!在国产接口调试工具APIPOST中使用Mock

    APIPOST可以让你在没有后端程序的情况下能真实地返回接口数据,你可以用APIPOST实现项目初期纯前端的效果演示,也可以用APIPOST实现开发中的数据模拟从而实现前后端分离.在使用APIPOST ...

  8. ABP中单元测试的技巧:Mock和数据驱动

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:虽然ABP为大家提供了测试的脚手架了,不过有些小技巧还是需要自己探索的. ASP.NE ...

  9. 使用Python中的mock模块进行单元测试

    在进行单元测试的时候,有时候会遇到这种情况: 出于某些原因,我们不想测试某一部分内容,但是我们想要测试的部分却依赖这部分内容. 这时候,可以使用mock模块来模拟调用这部分内容,并给出返回结果,举例如 ...

随机推荐

  1. monkey在指定的activity里面运行

    下载包地址:链接: https://pan.baidu.com/s/1Wk2eOj3saZx71Mx6pT2L4Q 提取码: gupa 运行方式:步骤1: 将工具下载下来放到本地目录下,解压步骤2:配 ...

  2. <UnityTheGreat><001>获取指定目录下指定类型的所有文件的名称

    #region Environment Windows 10 Unity 2019.4.16f1c1 LTS VSCode 1.52 https://github.com/MirzkisD1Ex0/U ...

  3. html 05-HTML标签图文详解(二)

    05-HTML标签图文详解(二) #本文主要内容 列表标签:<ul>.<ol>.<dl> 表格标签:<table> 框架标签及内嵌框架<ifram ...

  4. RocketMQ源码分析 broker启动,commitlog、consumequeue、indexfile、MappedFileQueue、MappedFile之间的关系以及位置说明

    目录 1.MappedFile类属性说明 1.1.MappedFile类属性如下 1.2.MappedFile构造器说明 2.MappedFileQueue类说明 2.1.属性说明 2.2.Mappe ...

  5. Wordpress Polylang 翻译自定义格式

    WordPress 多语言插件 Polylang 主题函数参考 重要:使用一个函数之前,你必须检查函数是否存在,否则,你的网站可能会在 Polylang 更新之前遇到致命错误(因为 WordPress ...

  6. Azure Service Bus(二)在NET Core 控制台中如何操作 Service Bus Queue

    一,引言 上一篇讲到关于 Azure ServiceBus 的一些概念,讲到 Azure Service Bus(服务总线),其实也叫 "云消息服务",是微软在Azure 上提供的 ...

  7. 一场由fork引发的超时,让我们重新探讨了Redis的抖动问题

    摘要:一次由fork引发的时延抖动问题. 背景介绍 华为云数据库GaussDB(for Redis) 是一款基于计算存储分离架构,兼容Redis生态的云原生NoSQL数据库:它依靠共享存储池实现了强一 ...

  8. 让你轻松掌握 Python 中的 Hook 钩子函数

    1. 什么是Hook 经常会听到钩子函数(hook function)这个概念,最近在看目标检测开源框架mmdetection,里面也出现大量Hook的编程方式,那到底什么是hook?hook的作用是 ...

  9. Linux腾讯云下安装mysql

    百度云盘下载地址https://pan.baidu.com/s/1MqUEdeqZuQbq-veLuVItQQ 将下载好的mysql-5.7.14-linux-glibc2.5-x86_64.tar. ...

  10. 【kinetic】操作系统探索总结(八)键盘控制

    如果尝试过前面的例子,有没有感觉每次让机器人移动还要在终端里输入指令,这也太麻烦了,有没有办法通过键盘来控制机器人的移动呢?答案室当然的了.我研究了其他几个机器人键盘控制的代码,还是有所收获的,最后移 ...