基于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 = Logger(MatchingServiceImpl.class);
@Autowired
private QuoteService quoteService;
...
public MatchingResult matching(MatchingOrder buyOrder, MatchingOrder sellOrder) {
int currentPrice = ProductCode());
MatchingResult result = new MatchingResult();
if (sellOrder != null && buyOrder != null &&
...
}
}
matching⽅法中的ProductCode());要访问Redis获取当前报价,这⾥我们需要把外部依赖quoteService mock掉,控制getCurrentPriceByProduct⽅法的返回值。使⽤mockito可以做到,具体如下:
测试类MatchingServiceImplTest
public class MatchingServiceImplTest extends MockitoBasedTest {
/**
* 被@Mock标注的对象会⾃动注⼊到被@InjectMocks标注的对象中
*/
@Mock
private QuoteService quoteService;
/**
* <pre>
* 被测对象,⽤@InjectMocks标注,那些被@mock标注的对象就会⾃动注⼊其中。
* 另⼀个注意点是这⾥的MatchingServiceImpl是直接new出来(Mockito 1.9版本后不new也可以),⽽不是通过spring容器注⼊的。因为这⾥我不需要从spring容器中  * 获得其他依赖,不需要database ,redis ,zookeeper,mq,啥都不依赖,所以直接new
* </pre>
*/
@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.CurrentPriceByProduct(Mockito.anyString())).thenReturn(900);
MatchingResult result = matchingService.matching(buyOrder, sellOrder);
org.junit.Assert.assertEquals(true, result.isSuccess());// 断⾔撮合是否成功
org.junit.Assert.assertEquals(20, TradeCount());// 断⾔成交数量
org.junit.Assert.assertEquals(900, TradePrice()); // 断⾔最新报价是否符合预期
}
Case2. 被测函数A调⽤被测类其他函数B,怎么控制函数B的返回值?
⽐如,MatchingServiceImpl中有个函数startBuyProcess,它⾥⾯调⽤了该类中的其他函数,如getTopSellOrder,matching,如何
控制这两个函数的返回值?
这⾥要解决的问题其实是怎么对⼀个类”部分mock”–被测类的被测⽅法(如startBuyProcess)要真实执⾏,⽽另⼀些⽅法(如
getTopSellOrder)则是要打桩(不真正进去执⾏)。
被测类MatchingServiceImpl
protected void startBuyProcess(MatchingOrder buyOrder, boolean waitForMatching) {
while (true) {
//对⼿⽅最优价
MatchingOrder topSellOrder = ProductCode());
MatchingResult matchingResult = matching(buyOrder,topSellOrder);
if(matchingResult.isSuccess()) {
doMatchingSuccess(buyOrder,topSellOrder,matchingResult,MatchingType.BUY);
Count() <= 0) {
break;
}
}else {
if(waitForMatching) {
/
/加⼊待撮合队列
addToMatchingBuy(buyOrder);
}else {springboot是啥
//撤单
sendCancleMsg(buyOrder);
}
break;
}
}
}
利⽤Mockito.spy()可以做到“部分Mock”
测试类stStartBuyProcess_InCaseOfMatchingSuccess
/**
*
* 测试StartBuyProcess⽅法在撮合成功后的处理是否符合预期,即测试startBuyProcess⽅法进⼊下⾯这个判断分⽀后的⾏为
* {@link MatchingServiceImpl#startBuyProcess(MatchingOrder, boolean)}
*
* <pre>
* if (matchingResult.isSuccess()) {
*
*  doMatchingSuccess(buyOrder, topSellOrder, matchingResult, MatchingType.BUY);
*
*  if (Count() <= 0) {
*    break;
*  }
* }
* </pre>
*
*/
@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可⽤来校验,某个类的⽅法被执⾏过多少次,这⾥是校验First是否被执⾏过1次
Mockito.verify(jedisClient, Mockito.times(1)).zremFirst(Mockito.anyString());
org.junit.Assert.assertEquals(3, Count());
org.junit.Assert.assertEquals(0, Count());
}
spy的⽤法已经演⽰完毕,下⾯从testStartBuyProcess_InCaseOfMatchingSuccess说下单元测试的“粒度”。testStartBuyProcess_InCaseOfMatchingSuccess的⽬的是想测doMatchingSuccess,我们费了很⼤劲才把前⾯的⼀堆准备⼯作做完,才能去测doMatchingSuccess。
更好的实践应该是另起测试⽅法去单独测doMatchingSuccess,关注点也集中很多,doMatchingSuccess覆盖完了,再测startBuyProcess其实就只是覆盖下它本⾝的判断分⽀就⾏了。覆盖率照样达到,⽽且测试代码也更容易维
护,testStartBuyProcess_InCaseOfMatchingSuccess由于考虑的职责太多,它很容易受到变化的影响,细⼩的东西改变,可能就会影响它的正常⼯作。
引⼊测试框架Maven依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId&kito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.5.RELEASE</version>
<scope>test</scope>
</dependency>
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
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。