1 前言
編寫 JAVA 單元測試用例,即把一段復(fù)雜的代碼拆解成一系列簡單的單元測試用例,并且無需啟動服務(wù),在短時間內(nèi)測試代碼中的處理邏輯。寫好 Java 單元測試用例,其實就是把 “復(fù)雜問題簡單化,建單問題深入化 “。在編寫的過程中, 我們也可以對自己的代碼進(jìn)行一個二次檢查。
以下是我總結(jié)的一些編寫單元測試的好處:
1. 測試代碼邏輯時,不需要啟動整個應(yīng)用。
2. 單元測試可以覆蓋邊界值
3. 提高原有代碼的復(fù)用
4. 可以有效避免代碼改動后,對原有邏輯的潛在影響
2 準(zhǔn)備環(huán)境
Mockito 是目前最普遍的單元測試模擬框架。Mockito 可以模擬應(yīng)用中依賴的復(fù)雜對象,從而把測試對象和依賴對象隔離開。PowerMock 為 Mockito 提供了擴展功能。為模擬靜態(tài)方法,final 類,和私有方法等。我們選擇使用以 Mockito 為主,PowerMock 為輔的框架來做單元測試。
2.1 引入 Mockito 和 PowerMock 包,在 pom.xml 文件中加入以下依賴:
<properties>
<powermock.version>2.0.9</powermock.version>
</properties>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
PowerMock 目前最新版本為 2.0.9【PowerMock 鏈接】由于 PowerMock 包中已經(jīng)包含了對應(yīng)的 Mockito 和 JUnit 包,所以無需再單獨引入。
3 一些常用的 mock 語句
3.1 模擬指定類的對象實例,用于模擬依賴對象(類成員)
在 Spring 中,這些成員對象通過 @Autowire,@Resource,@Value 等方式注入,可能涉及到環(huán)境配置或者依賴第三方接口。在單元測試中,不是我們關(guān)注的點,所以可以用 mock 模擬
//方法一
Mockito.mock(OrderInfo.class);
//方法二
@Mock
private OrderInfo orderInfo;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
}
3.2 定義被測試對象
把被測試服務(wù)類進(jìn)行實例化
@InjectMocks
private OrderServiceImpl orderService;
3.3 模擬枚舉類型 / 靜態(tài)方法
需要把對應(yīng)的模擬類放在 @PrepareForTest 中
//必須添加@RunWith和@PrepareForTest在類前
@RunWith(PowerMockRunner.class)
@PrepareForTest(OrderTypeEnum.class)
//在@Before中添加枚舉mock
@Before
public void beforeTest() {
mockStatic(OrderTypeEnum.class);
}
3.4 模擬依賴方法
在模擬完依賴的參數(shù)和返回值后,可以利用 Mockito 功能,進(jìn)行依賴方法的模擬。如果模擬對象還有方法調(diào)用,則需要模擬這些依賴對象的方法。
/***
when.thenReturn 和 doReturn.when是兩種實現(xiàn)方式
只有在使用@Spy時才會有區(qū)別
參考鏈接:https://www.imooc.com/wenda/detAIl/594190#id_653606
***/
//模擬枚舉的方法調(diào)用
when(OrderTypeEnum.getByValue(anyInt())).thenReturn(100);
//模擬依賴對象的依賴方法調(diào)用
doReturn(resultInfoDTO).when(orderInfoService).getLastOrderInfo(orderInfoDTO);
3.5 模擬構(gòu)造方法
PowerMock 提供了對構(gòu)造方法的模擬,但是需要把構(gòu)造方法的類放在 @PrepareForTest 中
//必須在@PrepareForTest中添加對應(yīng)類
@PrepareForTest({OrderTypeEnum.class, OrderServiceImpl.class})
whenNew(OrderInfoDTO.class).withNoArguments().thenReturn(orderInfoDTO);
3.6 驗證方法調(diào)用次數(shù)
被測方法調(diào)用后,一些方法會出現(xiàn)調(diào)用多次或根據(jù)不同條件進(jìn)行不同次數(shù)的調(diào)用。此時,可以根據(jù)驗證方法調(diào)用次數(shù),確定代碼的有效性
verify(orderInfoService,times(1)).getLastOrderInfo(orderInfoDTO);
3.7 驗證返回值
對于方法調(diào)用后的出參,我們會有一定的預(yù)期。所以,可以根據(jù)校驗返回值是否符合預(yù)期,確保返回值的正確性
Assert.assertEquals(result, "123");
3.8 驗證異常對象
JUnit 的 @Test 注解提供了一個 expected 屬性,可以指定一個期望的異常類型,用于捕獲異常并驗證其異常類型。【注】:只能驗證異常類型,不能驗證異常信息。
@Test(expected = BPLException.class)
4 單測舉例
下面是一個本地方法的單元測試用例,方法中調(diào)用了外部接口,并且其中包含了枚舉值的使用。
源方法即需要單測方法:
首先,是單元測試時一些必要的初始化:
4.1 單測場景一(確定接口調(diào)用,并返回值正確):
通過 verify 方法來確定接口是否調(diào)用過,并且只調(diào)用過 1 次。
通過 assert 來確認(rèn)返回值是否滿足預(yù)期
4.2 單測場景二(必要異常是否拋出):
通過在 @Test 注解上加入 expected 屬性,測試當(dāng)接口返回值為空時,是否可以拋出異常
4 總結(jié)
編寫單元測試在開發(fā)中的地位舉足輕重。在開發(fā)過程中,避免不了優(yōu)化或重構(gòu)歷史代碼。單元測試,在一定程度上可以幫助測試更新后邏輯,以及潛在調(diào)用鏈。另外也分享一些鏈接,希望可以幫助大家完成從 0 到 1 的搭建。
5 參考資料
- Java 編程技巧之單元測試用例編寫流程:https://mp.weixin.qq.com/s/hX_RIYs-nBnqVwdq5B4rhg
- powerMock 的 Git 鏈接:https://Github.com/powermock/powermock
- powerMock 簡介:https://www.baeldung.com/intro-to-powermock
- 避免 Install 的時候 Skip test: https://maven.Apache.org/plugins-archives/maven-surefire-plugin-2.12.4/examples/skipping-test.html
作者:京東物流 牟佳義
來源:京東云開發(fā)者社區(qū) 自猿其說 Tech 轉(zhuǎn)載請注明來源