單元測試——編寫手冊
1.簡述
本文主要針對如何使用Junit編寫單元測試進(jìn)行描述
文中的實例基于Junit 4
所謂單元測試,即是指針對程序中的一些單元進(jìn)行測試的方法
這些單元在Junit中的最小單位為方法
借助單元測試,我們可以輕松地單獨(dú)測試程序中的某一個邏輯片段而不需要在意程序的外部依賴和其它邏輯
接口測試 | 單元測試 |
---|---|
只能以接口為維度進(jìn)行測試 | 只需被測試的單元邏輯正常即可 |
工程必須編譯通過并打包進(jìn)行部署 | 可以不依賴外部,測試進(jìn)度不再受制于外部條件 |
工程的外部依賴(數(shù)據(jù)庫、調(diào)用的服務(wù)等)必須就緒 | 可以以方法為維度進(jìn)行測試 |
難以測試復(fù)雜的邏輯分支,為測試數(shù)據(jù)需要調(diào)整各個數(shù)據(jù)源(數(shù)據(jù)庫、緩存、消息隊列) | 可以根據(jù)單元的邏輯復(fù)雜程度編排測試用例數(shù)量,測試使用的數(shù)據(jù)可以自由調(diào)整 |
2.創(chuàng)建測試用例
2.1工程準(zhǔn)備
-
確保工程的maven依賴中包含junit,版本至少為4.12,一般包含在spring-boot-starter-test中;
-
確保工程的目錄中包含src/test/java和src/test/resource;
*部分工程沒有src/test的相關(guān)包,需要手動創(chuàng)建
*src/test/java主要用于存放測試用例和測試相關(guān)的類
*src/test/resoutces主要用于存放測試使用的配置和資源文件
2.2 編寫測試啟動類
許多工程在啟動時需要加載許多配置類,與外部系統(tǒng)進(jìn)行連接,非常復(fù)雜
為了使單元測試更加輕便,我們可以編寫單元測試專用的啟動類,屏蔽一些不相關(guān)的啟動項
若你的工程啟動和外部連接依賴本身就很簡單,可以省略這一步,不寫測試啟動類則執(zhí)行測試時默認(rèn)使用 src/main/java下的啟動類
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(basePackages = { "需要掃描的包名" }, excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { 掃描的包中需要排除的類 }) })
@SpringBootApplication(exclude = { 需要排除的一些啟動時自動配置類 })
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
這個啟動類中常用的配置如下
- @ComponentScan
用于掃描指定的包,excludeFilters用于排除掃描的包中不需要的Bean - @SpringBootApplication
用于排除一起自動配置的啟動類,防止諸如數(shù)據(jù)庫、Mongo等啟動類進(jìn)行外部連接
測試的啟動類與工程的啟動類類似,它通常防止在src/test/java下的項目根包中
其中“需要掃描的包名”和“掃描的包中需要排除的類”根據(jù)具體的工程決定,可按需將工程中的一些啟動時配置類排除在外(Cache配置等),保證你測試的類以及它們的直接依賴被掃描到即可
“需要排除的一些啟動時自動配置類”可參考如下對照表按需排除
作用 | 全類名 |
MongoDB自動配置 | org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration |
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration | |
數(shù)據(jù)源自動配置 | org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration |
Hibernate注解自動配置 | org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration |
啟動類的掃描配置決定了測試用例的范圍以及啟動速度
可以按需編寫多個不同掃描范圍的啟動類以適配不同的測試需求
2.3 構(gòu)建抽象測試類
根據(jù)啟動類創(chuàng)建一個抽象測試類是個好方法,它能夠讓你在創(chuàng)建測試用例時通過選擇繼承的父類直接確定使用的測試啟動配置。
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = 啟動類)
public abstract class BaseTest {
}
@SpringBootTest
用于指定測試的一些主要參數(shù),主要是classes,用于指定關(guān)聯(lián)的啟動類,這樣繼承自這個類的所有子類都會使用相同的啟動類
2.4 使用測試專用的配置
就像在工程部署時我們可以指定不同環(huán)境的配置文件一樣;
執(zhí)行測試時我們也可以使用不同的配置文件;
使用自定義測試啟動類的情況下將配置文件放置在src/test/resource下則測試類默認(rèn)會使用該包下的配置和資源文件;
由于測試啟動類中通常會排除許多與測試目標(biāo)無關(guān)的類,所以測試專用的配置文件可以更加輕量化,哪些被排除的類所引用的配置都可以省略;
2.5 新建Test類
針對一個被測類創(chuàng)建一個測試類
測試類通常以被測類的名字+Test進(jìn)行命名,并放置在與src/test/java下與被測類同包名的包中
- 根據(jù)被測類創(chuàng)建測試類
- 配置測試類的參數(shù),通常這些參數(shù)會按照約定給出一些默認(rèn)值;
- 選擇要測試的方法,注意:junit測試只能對非private方法進(jìn)行測試DemoServiceTest,測試用例在src/test/java下的被測類同名包中;此時可以選擇繼承的父類,根據(jù)測試需要進(jìn)行繼承
- 生成測試類,得到一個單元測試類的骨架,包含了選擇的方法的測試用例
編寫用例
3.1 了解Mock測試
一個Bean通常會形成一個依賴樹,這種發(fā)散的依賴結(jié)構(gòu)導(dǎo)致我們在測試一個類的邏輯時其實是連同它所依賴的邏輯一同進(jìn)行測試
此時使用Mock測試就可以解決依賴過多,邏輯復(fù)雜的問題
Mock以為模擬、虛擬,就是將原有的邏輯進(jìn)行模擬,使用規(guī)劃的Mock方案邏輯進(jìn)行替代
在Mock測試中,我們通常對測試對象的所有直接依賴進(jìn)行Mock,被Mock的直接依賴將變成只有方法簽名的空殼
被調(diào)用時它們不會再調(diào)用間接依賴,也不會執(zhí)行原有的邏輯,只會根據(jù)Mock方案進(jìn)行返回
這樣在測試測試對象時我們就不在需要關(guān)心它負(fù)載的間接依賴關(guān)系和所有依賴的內(nèi)部邏輯了,只需要專注于當(dāng)前測試對象的邏輯即可
3.2 完全Mock依賴
這種測試僅測試測試對象內(nèi)部的邏輯,屏蔽所有測試對象的依賴
適用于測試內(nèi)部邏輯較為復(fù)雜的對象,或依賴較為復(fù)雜的對象
樣例:
注意
- @MockBean標(biāo)記的依賴的方法默認(rèn)變?yōu)榭辗椒ㄇ曳祷刂禐閚ull,一定要配合mock方案才能執(zhí)行邏輯和獲取返回值(參照FAQ-@MockBean與@SpyBean)
- mock方案的編寫參照FAQ-mock方案
- 需要調(diào)用@MockBean標(biāo)記的類中無返回值的方法時可以不編寫mock方案,但一定要校驗調(diào)用是否執(zhí)行(參照FAQ-驗證調(diào)用是否執(zhí)行)
3.3 完全真實依賴
這種類型的測試同時測試測試對象及全部其依賴的邏輯,針對實際調(diào)用流程進(jìn)行測試
適用于測試一個完整的流程以及測試調(diào)用鏈路正確性
樣例
注意
- 這種測試方式與Mock毫無關(guān)系,為常規(guī)的方法邏輯調(diào)用
- 測試的結(jié)果與輸入的數(shù)據(jù)、持久化的數(shù)據(jù)、程序邏輯直接相關(guān),因此需要事先準(zhǔn)備好配套得入?yún)ⅰ?shù)據(jù)庫腳本/Redis緩存并熟悉程序的邏輯,以此推導(dǎo)出正確的處理結(jié)果
3.4 Mock與真實依賴相結(jié)合
這種類型的測試同時測試測試對象及其部分依賴的邏輯,屏蔽部分較為復(fù)雜的依賴同時對實際調(diào)用流程進(jìn)行測試
適用于測試一個部分依賴較為復(fù)雜的流程以及測試調(diào)用鏈路正確性
樣例:
注意
- @SpyBean標(biāo)記的依賴的方法默認(rèn)為真實邏輯,若配合mock方案則在入?yún)⑴cmock方案一致時根據(jù)mock方案的設(shè)置進(jìn)行返回(參照FAQ-@MockBean與@SpyBean)
- mock方案的編寫參照FAQ-mock方案
- 需要調(diào)用@SpyBean標(biāo)記的類的方法的真實邏輯時可以不編寫mock方案
- 測試的結(jié)果與輸入的數(shù)據(jù)、持久化的數(shù)據(jù)、程序邏輯、mock方案均相關(guān),因此需要事先準(zhǔn)備好配套得入?yún)ⅰ?shù)據(jù)庫腳本/Redis緩存并熟悉程序的邏輯和mock方案的邏輯,以此推導(dǎo)出正確的處理結(jié)果
- 雖然@SpyBean標(biāo)記的類在沒有設(shè)置mock方案的情況下原則上與原類的邏輯一致,但由于底層實現(xiàn)原理的種種限制,它與原生的類在某些情況下不能完全一致;若想直接測試完全真實依賴的場景,請參照完全使用真實依賴的測試章節(jié)的描述
3.5 混合測試
對一個測試對象中的不同方法采取不同的測試策略
對同一個測試對象的不同方法根據(jù)需求采取“完全Mock”或“完全真實”或“Mock與真實結(jié)合”的方式;沒有對同一個測試 對象編寫多個不同類型測試用例的需求
注意
- 此種測試是其他3種的復(fù)合形式,單個用例內(nèi)的書寫參照相應(yīng)章節(jié)的描述
- 此種測試的測試對象的依賴需要使用@SpyBean的方式進(jìn)行標(biāo)記,否則難以執(zhí)行真實邏輯(參照FAQ-@MockBean與@SpyBean)
4. FAQ
4.1 @MockBean與@SpyBean
在進(jìn)行mock測試或混合測試時可以看到這2中不同的設(shè)定測試對象直接依賴的注解
/ | 相同 | 不同 |
---|---|---|
@MockBean | 提供mock測試能力mock方案的作用域都在一個@Test內(nèi) | 默認(rèn)將標(biāo)記該注解的類及其依賴的方法全部掏空,直接調(diào)用將沒有任何執(zhí)行邏輯并返回null(如果有返回值) |
@SpyBean | 提供mock測試能力mock方案的作用域都在一個@Test內(nèi) | 默認(rèn)將標(biāo)記該注解的類以及依賴的方法保持原樣,直接調(diào)用將按照方法原本的邏輯執(zhí)行并返回;可以按照混合測試的需求靈活決定是否mock |
/ | 使用場景 |
---|---|
@MockBean | 適用于標(biāo)記的依賴需要被完全mock的場景,不配合mock方案使用易引發(fā)空指針異常 |
@SpyBean | 適用于標(biāo)記的依賴只有部分邏輯需要mock的場景,不配合mock方案則執(zhí)行原方法,配合mock方案則執(zhí)行mock方案 |
4.2 mock方案
在進(jìn)行mock測試時需要提前針對被mock的邏輯進(jìn)行規(guī)劃
參照如下寫法:
Mockito.doReturn(mockData).when(demoService).function(Matchers.eq(arg1), Matchers.eq(arg2));
Mockito.doNothing().when(demoService).function(Matchers.eq(arg1), Matchers.eq(arg2));
Mockito.doThrow(exception).when(demoService).function((Matchers.eq(arg1), Matchers.eq(arg2));
關(guān)鍵詞 | 解釋 |
---|---|
doReturn(mockData) | 模擬方法正常返回數(shù)據(jù) |
doNothing() | 模擬方法未執(zhí)行(可用于沒有返回值的方法) |
doThrow(exception) | 模擬方法執(zhí)行時拋出異常 |
demoService | 被@MockBean或@SpyBean標(biāo)記的測試對象的直接依賴 |
function、arg1、arg2 | 直接依賴的方法名及其參數(shù) |
Matchers.eq() | org.mockito.Matchers提供的一些更靈活的mock調(diào)用時參數(shù)驗證方法;驗證通過時方法返回模擬的返回數(shù)據(jù),驗證不通過時方法按照@MockBean或@SpyBean的默認(rèn)策略執(zhí)行 |
4.3 同名方法調(diào)用多次
同名方法設(shè)置多個mock方案時,不同入?yún)㈩A(yù)期的方案都會保留,相同入?yún)㈩A(yù)期的方案以最后一個為準(zhǔn)
例,設(shè)置了如下mock方案:
@MockBean
private DemoService demoService;
@Test
public void testFunction() {
// 省略其他步驟,此處只展示mock方案設(shè)置
// 方案1
Mockito.doReturn(demoDataA).when(demoService).callFun(eq("alpha"));
// 方案2
Mockito.doReturn(demoDataB).when(demoService).callFun(eq("beta"));
// 方案3
Mockito.doCallRealMethod().when(demoService).callFun(eq("beta"));
}
方案1,3會生效,方案2會被方案3覆蓋而失效
調(diào)用demoService.callFun(String)方法時:
若入?yún)?alpha",則返回demoDataA;
若入?yún)?beta",則執(zhí)行該方法的真實邏輯并返回;
若入?yún)槠渌?,則執(zhí)行空方法并返回null(@MockBean的默認(rèn)方案);
在上述場景的基礎(chǔ)上,若被測方法中存在同一方法調(diào)用多次且入?yún)⑾嗤?,但根?jù)執(zhí)行順序返回值不同,則參照如下寫法(使用鏈?zhǔn)秸{(diào)用依序排布多個方案):
@MockBean
private DemoService demoService;
@Test
public void testFunction() {
// 省略其他步驟,此處只展示mock方案設(shè)置
// 方案1
Mockito.doReturn(demoDataA).when(demoService).callFun(eq("alpha"));
// 方案2(包含多次調(diào)用的方案)
Mockito.doReturn(demoDataB).doCallRealMethod().when(demoService).callFun(eq("beta"));
}
調(diào)用demoService.callFun(String)方法時:
若入?yún)?alpha",則返回demoDataA;
若入?yún)?beta"且是該入?yún)⒌牡?次調(diào)用,則返回demoDataB;
若入?yún)?beta"且是該入?yún)⒌牡?次調(diào)用,執(zhí)行該方法的真實邏輯并返回;
若入?yún)?beta"且是該入?yún)⒌牡?次及以上調(diào)用,執(zhí)行方案鏈中最后一個方案(例子中為調(diào)用真實邏輯);
若入?yún)槠渌瑒t執(zhí)行空方法并返回null(@MockBean的默認(rèn)方案)
4.4 驗證調(diào)用是否執(zhí)行
在mock測試中需要驗證mock方案標(biāo)記的方法是否按照預(yù)期的入?yún)?zhí)行過,若缺少這一步可能導(dǎo)致測試用例出現(xiàn)未預(yù)期的成功
例,一個用例按如下步驟執(zhí)行:
- 測試的依賴使用@SpyBean標(biāo)注
- 執(zhí)行到mock方案對應(yīng)的方法,入?yún)⑴cmock方案預(yù)期不一致
- 方法按照真實邏輯執(zhí)行
- 方法的返回值恰巧與預(yù)期一致
- 測試成功
上述例子的用例可能會在某次數(shù)據(jù)庫數(shù)據(jù)變動,外部鏈接中斷的場景下執(zhí)行失敗
增加驗證調(diào)用是否執(zhí)行的步驟可以解決該問題
寫法如下:文章來源:http://www.zghlxwxcb.cn/news/detail-407042.html
Mockito.verify(demoService).function(Matchers.eq(arg1), Matchers.eq(arg2));
與mock方案中的寫法非常相似
當(dāng)同名方法調(diào)用多次且入?yún)⒁恢聲r采用如下寫法:文章來源地址http://www.zghlxwxcb.cn/news/detail-407042.html
Mockito.verify(demoService, new Times(2)).function(Matchers.eq(arg1), Matchers.eq(arg2));
到了這里,關(guān)于測試用例-單元測試的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!