今天就跟大家聊聊有關怎么在Springboot中使用Junit和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;
/**
* <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.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)}
*
* <pre>
* if (matchingResult.isSuccess()) {
*
* doMatchingSuccess(buyOrder, topSellOrder, matchingResult, MatchingType.BUY);
*
* if (buyOrder.getCount() <= 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可用來校驗,某個類的方法被執行過多少次,這里是校驗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依賴
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</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看完上述內容,你們對怎么在Springboot中使用Junit和Mockito實現單元測試有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。