基于Springboot+Junit+Mockito做单元测试
前言
前面的两篇文章讨论过《 为什么要写单元测试,何时写,写多细
》和《单元测试规范》,这篇文章介绍如何使用Springboot+Junit+Mockito做单元测试,案例选取撮合交易的一个类来做单元测试。
单元测试前先理解需求
要写出好的单测,必须先理解了需求,只有知道做什么才能知道怎么测。但本文主要讲mockito的用法,无需关注具体需求。所以本节略去具体的需求描述。
隔离外部依赖
Case1. 被测类中被@Autowired 或 @Resource 注解标注的依赖对象,如何控制其返回值
以被测方法 MatchingServiceImpl.java的matching(MatchingOrder buyOrder, MatchingOrder sellOrder)为例
- 被测类MatchingServiceImpl
public class MatchingServiceImpl implements MatchingService {
private static final Logger log = LoggerFactory.getLogger(MatchingServiceImpl.class);
@Autowired
private QuoteService quoteService;
...
public MatchingResult matching(MatchingOrder buyOrder, MatchingOrder sellOrder) {
int currentPrice = quoteService.getCurrentPriceByProduct(buyOrder.getProductCode());
MatchingResult result = new MatchingResult();
if (sellOrder != null && buyOrder != null &&
sellOrder.getPrice() <= buyOrder.getPrice()) {
...
}
}
matching方法中的quoteService.getCurrentPriceByProduct(buyOrder.getProductCode());要访问Redis获取当前报价,这里我们需要把外部依赖quoteService mock掉,控制getCurrentPriceByProduct方法的返回值。使用mockito可以做到,具体如下:
- 测试类MatchingServiceImplTest
public class MatchingServiceImplTest extends MockitoBasedTest {
/**
* 被@Mock标注的对象会自动注入到被@InjectMocks标注的对象中
*/
@Mock
private QuoteService quoteService;
/**
** 被测对象,用@InjectMocks标注,那些被@mock标注的对象就会自动注入其中。
* 另一个注意点是这里的MatchingServiceImpl是直接new出来(Mockito 1.9版本后不new也可以),而不是通过spring容器注入的。因为这里我不需要从spring容器中
* 获得其他依赖,不需要database ,redis ,zookeeper,mq,啥都不依赖,所以直接new
**/
@InjectMocks
private MatchingServiceImpl matchingService = new MatchingServiceImpl();
@Test
public void testMatching_SuccessWhenCurrentPriceBetweenBuyPriceAndSellPrice() {
MatchingOrder buyOrder = new MatchingOrder();
buyOrder.setPrice(1000);
buyOrder.setCount(23);
MatchingOrder sellOrder = new MatchingOrder();
sellOrder.setPrice(800);
sellOrder.setCount(20);
// 方法打桩(Method stubbing)
// when(x).thenReturn(y) :当指定方法被调用时返回指定值
Mockito.when(quoteService.getCurrentPriceByProduct(Mockito.anyString())).thenReturn(900);
MatchingResult result = matchingService.matching(buyOrder, sellOrder);
org.junit.Assert.assertEquals(true, result.isSuccess());// 断言撮合是否成功
org.junit.Assert.assertEquals(20, result.getTradeCount());// 断言成交数量
org.junit.Assert.assertEquals(900, result.getTradePrice()); // 断言最新报价是否符合预期
}Case2. 被测函数A调用被测类其他函数B,怎么控制函数B的返回值?
比如,MatchingServiceImpl中有个函数startBuyProcess,它里面调用了该类中的其他函数,如getTopSellOrder,matching,如何控制这两个函数的返回值?
这里要解决的问题其实是怎么对一个类”部分mock”–被测类的被测方法(如startBuyProcess)要真实执行,而另一些方法(如getTopSellOrder)则是要打桩(不真正进去执行)。
- 被测类MatchingServiceImpl
protected void startBuyProcess(MatchingOrder buyOrder, boolean waitForMatching) {
while (true) {
//对手方最优价
MatchingOrder topSellOrder = getTopSellOrder(buyOrder.getProductCode());
MatchingResult matchingResult = matching(buyOrder,topSellOrder);
if(matchingResult.isSuccess()) {
doMatchingSuccess(buyOrder,topSellOrder,matchingResult,MatchingType.BUY);
if(buyOrder.getCount() <= 0) {
break;
}
}else {
if(waitForMatching) {
//加入待撮合队列
addToMatchingBuy(buyOrder);
}else {
//撤单
sendCancleMsg(buyOrder);
}
break;
}
}
}
利用Mockito.spy()可以做到“部分Mock”
- 测试类MatchingServiceImplTest.testStartBuyProcess_InCaseOfMatchingSuccess
/**
*
* 测试StartBuyProcess方法在撮合成功后的处理是否符合预期,即测试startBuyProcess方法进入下面这个判断分支后的行为
* {@link MatchingServiceImpl#startBuyProcess(MatchingOrder, boolean)}
*
** if (matchingResult.isSuccess()) {
*
* doMatchingSuccess(buyOrder, topSellOrder, matchingResult, MatchingType.BUY);
*
* if (buyOrder.getCount() <= 0) {
* break;
* }
* }
**
*/
@Test
public void testStartBuyProcess_InCaseOfMatchingSuccess() {
MatchingOrder buyOrder = new MatchingOrder();
buyOrder.setPrice(700);
buyOrder.setCount(23);
// 用Mockito.spy()对matchingService进行部分打桩
matchingService = Mockito.spy(matchingService);
MatchingResult firstMatchingResult = new MatchingResult();
firstMatchingResult.setSuccess(true);
firstMatchingResult.setTradeCount(20);
MatchingResult secondMatchingResult = new MatchingResult();
secondMatchingResult.setSuccess(false);
// doReturn(x).when(obj).method() 对方法打桩,打桩后,程序执行这些方法时将按照预期返回指定值,未被打桩的方法将真实执行
// 两个doReturn表示第一次调用matchingService.matching时返回firstMatchingResult,第二次调用返回secondMatchingResult
// 因为startBuyProcess里有个while循坏,可能会多次执行matching方法
Mockito.doReturn(firstMatchingResult).doReturn(secondMatchingResult).when(matchingService)
.matching(Mockito.any(MatchingOrder.class), Mockito.any(MatchingOrder.class));
MatchingOrder sellOrder = new MatchingOrder();
sellOrder.setPrice(600);
sellOrder.setCount(20);
// 对getTopSellOrder方法打桩
Mockito.doReturn(sellOrder).when(matchingService).getTopSellOrder(Mockito.anyString());
// 对外部依赖jedis的方法进行打桩
Mockito.when(jedisClient.incrBy(Mockito.anyString(), Mockito.anyLong())).thenReturn(0L);
// startBuyProcess是被测函数,不打桩,会真实执行
matchingService.startBuyProcess(buyOrder, true);
// 后面的校验和断言是测试doMatchingSuccess方法的行为的,这也是这个测试的目的
// verify可用来校验,某个类的方法被执行过多少次,这里是校验jedisClient.zremFirst是否被执行过1次
Mockito.verify(jedisClient, Mockito.times(1)).zremFirst(Mockito.anyString());
org.junit.Assert.assertEquals(3, buyOrder.getCount());
org.junit.Assert.assertEquals(0, sellOrder.getCount());
}spy的用法已经演示完毕,下面从testStartBuyProcess_InCaseOfMatchingSuccess说下单元测试的“粒度”。
testStartBuyProcess_InCaseOfMatchingSuccess的目的是想测doMatchingSuccess,我们费了很大劲才把前面的一堆准备工作做完,才能去测doMatchingSuccess。
更好的实践应该是另起测试方法去单独测doMatchingSuccess,关注点也集中很多,doMatchingSuccess覆盖完了,再测startBuyProcess其实就只是覆盖下它本身的判断分支就行了。覆盖率照样达到,而且测试代码也更容易维护,testStartBuyProcess_InCaseOfMatchingSuccess由于考虑的职责太多,它很容易受到变化的影响,细小的东西改变,可能就会影响它的正常工作。
引入测试框架Maven依赖
junit
junit
4.11
testorg.mockito
mockito-all
1.10.19
test org.springframework
spring-test
4.2.5.RELEASE
test
springboot+junit+mockito的上下文构建
- MockitoBasedTest
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestApplication.class)
public abstract class MockitoBasedTest {
@Before
public void setUp() throws Exception {
// 初始化测试用例类中由Mockito的注解标注的所有模拟对象
MockitoAnnotations.initMocks(this);
}
}
// 其他测试类继承MockitoBasedTest
【阅读原文...】
原文地址:https://www.colabug.com/2340314.html
基于Springboot+Junit+Mockito做单元测试的更多相关文章
- 使用 Junit + Mockito 实践单元测试
一.前言 相信做过开发的同学,都多多少少写过下面的代码,很长一段时间我一直以为这就是单元测试... @SpringBootTest @RunWith(SpringRunner.class) publi ...
- 基于mockito做有效的单元测试
概述 本文讲解的主要是有效和单元的思想,并不是说如何编写单元测试,用于改善和提高开发效率.编码风格.编码可读性和单测效率,不盲目追求覆盖率. 背景 现在很多单元测试只是利用@Test注解把代码或者整个 ...
- 基于spring-boot的应用程序的单元测试方案
概述 本文主要介绍如何对基于spring-boot的web应用编写单元测试.集成测试的代码. 此类应用的架构图一般如下所示: 我们项目的程序,对应到上图中的web应用部分.这部分一般分为Control ...
- JUnit + Mockito 单元测试(二)
摘自: http://blog.csdn.net/zhangxin09/article/details/42422643 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 入门 ...
- JUnit + Mockito 单元测试(二)(good)
import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; import java.util.Lis ...
- JUnit + Mockito 单元测试
原 JUnit + Mockito 单元测试(二) 2015年01月05日 17:26:02 sp42a 阅读数:60755 版权声明:本文为博主原创文章,未经博主允许不得转载. https://bl ...
- spring junit 做单元测试,报 Failed to load ApplicationContext 错误
spring junit 做单元测试,报 Failed to load ApplicationContext 错误. 查找了好一会,最后发现.@ContextConfiguration(locatio ...
- 基于spring-boot的社区社交微信小程序,适合做脚手架、二次开发
基于spring-boot的社区社交微信小程序,适合做脚手架.二次开发 代码地址如下:http://www.demodashi.com/demo/13867.html 1 概述 笔者做的一个后端基于s ...
- 基于spring-boot的应用程序的单元+集成测试方案
目录 概述 概念解析 单元测试和集成测试 Mock和Stub 技术实现 单元测试 测试常规的bean 测试Controller 测试持久层 集成测试 从Controller开始测试 从中间层开始测试 ...
随机推荐
- Ubuntu18.04+windows10双系统时间同步教程
前言: 系统安装windows10和Ubuntu18.04双系统后会出现时间不同步的情况,往往windows系统的时间会有错误,一般会有8个小时的误差. 原因: 主要因为本地时间与硬件时间的时差: 本 ...
- CC2540 / CC2541 竟然支持 Bluetooth BLE 5.0?
CC2540 / CC2541 竟然支持 Bluetooth BLE 5.0? 无意中发现 CC2541 的 BLE 协议栈更新了. BLE-STACK is Bluetooth 5.0 qualif ...
- 洛谷2375 BZOJ 3670动物园题解
题目链接 洛谷链接 我们发现题目要我们求的num[i]东西本质上其实是 求有多少以i结尾的非前缀且能与前缀匹配的字符串,而且要求字符串长度小于(i/2) 我们先不考虑字符串长度的限制,看所有以i结尾的 ...
- SGU 107 987654321 problem【找规律】
题目链接: http://acm.sgu.ru/problem.php?contest=0&problem=107 题意: 平方后几位为987654321的n位数有多少个 分析: 虽然说是水题 ...
- HashMap的运用 计算3个班级的总成绩和平均成绩
TestStudent.java package com.sxt.home; import java.util.ArrayList; import java.util.HashMap; import ...
- JavaScript 开发者都应该知道的十个概念
1. 原始值和引用值(Value vs. Reference) 理解对象.数组和函数是如何复制和传递到函数中的.了解引用值是被复制了什么,理解原始值是通过复制值来进行复制和传递的. 简析:ECMASc ...
- Linux 用户he用户组管理
8)系统中有一类用户称为伪用户(psuedo users). 这些用户在/etc/passwd 文件中也占有一条记录,但是不能登陆,因为他们的登陆shell 为空,他们的存在主要是方便系统管理,满足 ...
- HZOJ visit
对于前30%的数据,可以考虑dp,f[i][j][k]表示时间为i,在i,j位置的方案数,枚举转移即可.要注意的是可以走到矩阵外. 对于另外30%数据,考虑推一下式子,设向右走y步,左z,上s,下x. ...
- PHP怎么调用其他类的方法
2个PHP,这个PHP中的类调用另一个PHP中的类,如何调用.Java中是import ,php中是什么?还是用其他什么方法? 1.引用类:比如类名为product,则:include('...路径/ ...
- celery 计划任务使用
流程: 用户提交任务 --- > Celery --- > Broker 中间商(可以是数据库,redis) ---> 最后让celery 中的 worker 执行任务 1 单独使用 ...