
單元測(cè)試目的
維基百科對(duì)單元測(cè)試的定義:
單元測(cè)試(英語(yǔ):Unit Testing)又稱為模塊測(cè)試,是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來(lái)進(jìn)行正確性檢驗(yàn)的測(cè)試工作。程序單元是應(yīng)用的最小可測(cè)試部件。在過(guò)程化編程中,一個(gè)單元就是單個(gè)程序、函數(shù)、過(guò)程等;對(duì)于面向?qū)ο缶幊蹋钚卧褪欠椒?,包括基類(超類)、抽象類、或者派生類(子類)中的方法?
單元測(cè)試的目標(biāo)是隔離程序部件并證明這些單個(gè)部件是正確的。
- 畫(huà)外音:?jiǎn)卧獪y(cè)試是比較細(xì)粒度的測(cè)試,是對(duì)接口、方法、函數(shù)的測(cè)試,目的是保障代碼按照正確的方式去執(zhí)行,提高代碼質(zhì)量。
單元測(cè)試實(shí)施原則
Mock脫離數(shù)據(jù)庫(kù) + 不啟動(dòng)Spring + 優(yōu)化測(cè)試速度 + 不引入項(xiàng)目組件
單元測(cè)試不應(yīng)該依賴數(shù)據(jù),依賴外部服務(wù)或組件等,會(huì)對(duì)其他數(shù)據(jù)產(chǎn)生影響的情況。啟動(dòng)Spring容器,一般比較慢,可能會(huì)啟動(dòng)消息監(jiān)聽(tīng)消費(fèi)消息,定時(shí)任務(wù)的執(zhí)行等,對(duì)數(shù)據(jù)產(chǎn)生影響。
Mock測(cè)試就是在測(cè)試過(guò)程中,對(duì)那些當(dāng)前測(cè)試不關(guān)心的,不容易構(gòu)建的對(duì)象,用一個(gè)虛擬對(duì)象來(lái)代替測(cè)試的情形。
說(shuō)白了:就是解耦(虛擬化)要測(cè)試的目標(biāo)方法中調(diào)用的其它方法,例如:Service的方法調(diào)用Mapper類的方法,這時(shí)候就要把Mapper類Mock掉(產(chǎn)生一個(gè)虛擬對(duì)象),這樣我們可以自由的控制這個(gè)Mapper類中的方法,讓它們返回想要的結(jié)果、拋出指定異常、驗(yàn)證方法的調(diào)用次數(shù)等等。
減少單元測(cè)試對(duì)外部的依賴和副作用,提高單元測(cè)試效率
- 不使用 @Autowired,@Resource, 需要啟動(dòng) Spring 容器,測(cè)試速度慢,會(huì)產(chǎn)生副作用;
- 不使用 @SpringBootTest,@SpringBootTest(classes = Application.class), 這會(huì)啟動(dòng)整個(gè) SpringBoot 服務(wù)
- 不應(yīng)調(diào)用數(shù)據(jù)庫(kù),除非是做數(shù)據(jù)庫(kù)操作相關(guān)的測(cè)試,雖然可配置事務(wù)回滾,但大多數(shù)情況下還是會(huì)產(chǎn)生臟數(shù)據(jù)等問(wèn)題
- 使用Assert斷言,用于判斷某個(gè)特定條件下某個(gè)方法的行為,為了證明某段代碼的執(zhí)行結(jié)果和期望的一致
- 畫(huà)外音:?jiǎn)卧獪y(cè)試應(yīng)小而輕,提交測(cè)試效率,較少對(duì)外部的依賴,比如數(shù)據(jù)庫(kù)、Spring容器、網(wǎng)絡(luò)服務(wù)等,而只關(guān)心我們自己的代碼,通過(guò)Mock來(lái)解決對(duì)外部的依賴
Mockito的使用
基本使用
- 使用靜態(tài)方法 mock()
- 使用注解 @Mock 標(biāo)注
如果使用@Mock注解, 必須去觸發(fā)所標(biāo)注對(duì)象的創(chuàng)建. 可以使用 MockitoRule來(lái)實(shí)現(xiàn). 它調(diào)用了靜態(tài)方法MockitoAnnotations.initMocks(this) 去初始化這個(gè)被注解標(biāo)注的字段.或者也可以使用@RunWith(MockitoJUnitRunner.class).
“when thenReturn”和”when thenThrow”
模擬對(duì)象可以根據(jù)傳入方法中的參數(shù)來(lái)返回不同的值, when(….).thenReturn(….)方法是用來(lái)根據(jù)特定的參數(shù)來(lái)返回特定的值.
我們也可以使用像 anyString 或者 anyInt anyLong any 這樣的方法來(lái)定義某個(gè)依賴數(shù)據(jù)類型的方法返回特定的值.
“doReturn when” 和 “doThrow when”
doReturn(…).when(…)的方法調(diào)用和when(….).thenReturn(….)類似.對(duì)于調(diào)用過(guò)程中拋出的異常非常有用.而doThrow則也是它的一個(gè)變體.
常用注解
@Mock:對(duì)函數(shù)的調(diào)用均執(zhí)行mock(即虛假函數(shù)),不執(zhí)行真正部分。
@Spy:對(duì)函數(shù)的調(diào)用均執(zhí)行真正部分。
@InjectMocks:創(chuàng)建一個(gè)實(shí)例,簡(jiǎn)單的說(shuō)是這個(gè)Mock可以調(diào)用真實(shí)代碼的方法,使用@Mock(或@Spy)注解創(chuàng)建的mock將被注入到用該實(shí)例中。
Mockito中的Mock和Spy都可用于攔截那些尚未實(shí)現(xiàn)或不期望被真實(shí)調(diào)用的對(duì)象和方法,并為其設(shè)置自定義行為。二者的區(qū)別在于Mock不真實(shí)調(diào)用,Spy會(huì)真實(shí)調(diào)用。
@MockBean: 功能同 @Mock, 只是會(huì)將實(shí)例放入 Spring 容器管理
@SpyBean: 功能同 @Spy, 只是會(huì)將實(shí)例放入 Spring 容器管理
- Spy 和 Mock 生成的對(duì)象不受 Spring 管理
- Spy 調(diào)用真實(shí)方法時(shí),其它 bean 是無(wú)法注入的,要使用注入,要使用 SpyBean
- SpyBean 和 MockBean 生成的對(duì)象受 Spring 管理,相當(dāng)于自動(dòng)替換對(duì)應(yīng)類型 bean 的注入,比如 @Autowired、@Resource 等注入
最佳實(shí)踐
// 不使用 @SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class ExamAnswerComponentTest {
// 創(chuàng)建一個(gè)實(shí)例,會(huì)注入Mock變量
@InjectMocks
private ExamAnswerComponent examAnswerComponent = new ExamAnswerComponentImpl();
// 相關(guān)操作會(huì)被Mock掉
@Mock
private ExamAnswerCacheObjectiveDAO examAnswerCacheObjectiveDAO;
@Before
public void setUp() {
// 初始化Mock
MockitoAnnotations.initMocks(this);
// given...willReturn 指定方法參數(shù),模擬返回值
given(examAnswerCacheObjectiveDAO.selectByBizIdAndPaperAndQuestion(any(), any(), any()))
.willReturn(new ExamAnswerCacheObjectivePO());
given(examAnswerCacheObjectiveDAO.insert(any())).willReturn(1);
given(examAnswerCacheObjectiveDAO.updateUserAnswerById(any(), any())).willReturn(1);
}
@Test
public void saveOrUpdateAnswerCacheObjective() {
ExamAnswerCacheObjectivePO po = new ExamAnswerCacheObjectivePO();
po.setBizId(100000015L);
po.setBizType(9);
po.setUserAnswer("A");
po.setGroupPaperId(1000320L);
po.setQuestionId(1000042L);
po.setQuestionType(1);
int affect = examAnswerComponent.saveOrUpdateAnswerCacheObjective(po);
System.out.println("affect = " + affect);
Assert.assertTrue(affect > 0);
}
}