單元測試效率優(yōu)化:為什么要對(duì)程序進(jìn)行測試?測試有什么好處?
?單元測試(Unit Testing)又稱為模塊測試, 是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來進(jìn)行正確性檢驗(yàn)的測試工作。 程序單元是應(yīng)用的最小可測試部件。簡單來說,就是測試數(shù)據(jù)的穩(wěn)定性是否達(dá)到程序的預(yù)期。談到測試,我們?yōu)槭裁匆獙?duì)程序進(jìn)行測試呢?測試會(huì)為程序帶來什么好處呢?
單元測試的重要性
我們?nèi)粘i_發(fā)時(shí)可能在不經(jīng)意間寫錯(cuò),如果等到最后階段去檢驗(yàn)項(xiàng)目成果時(shí),發(fā)現(xiàn)有錯(cuò)誤,這時(shí)候我們很難找到Bug的源頭在哪里。我們都知道,有可能一處出錯(cuò)會(huì)導(dǎo)致步步錯(cuò)的情況。
測試就在我們的上述說法中,顯得尤為重要,當(dāng)我們做完項(xiàng)目的一個(gè)小模塊,先去測試一下這個(gè)小模塊是否正確或達(dá)到預(yù)期,如果錯(cuò)誤或者沒有達(dá)到預(yù)期就需要反復(fù)修改,直到正確或達(dá)到預(yù)期,也就是使用了單元測試。
單元測試的編碼規(guī)范一般涉及到以下內(nèi)容:
- 類名: 定義測試類,類名是由被測試類名Test構(gòu)成。例如:CalculatorTest;
- 包名:定義的測試類需要放在xxx.xxx.xxx.test包中。例如:package com.autodrive.test;
- 方法名: 測試方法的方法名有兩種定義方式test測試方法和測試方法。例如:testCheck和check;
- 返回值: 因?yàn)槲覀兊姆椒ㄖ皇窃陬愔袦y試,可以獨(dú)立運(yùn)行,所以不需要處理任何返回值,所以這里使用void。例如:public void check();
- 參數(shù)列表: 因?yàn)槲覀兊姆椒ㄊ怯脕頊y試的,至于參數(shù)列表的傳入是沒有必要的。我們?cè)跍y試的時(shí)候自行傳入需要的參數(shù)測試即可。所以在此參數(shù)列表為空。例如:例如:public void check();
- @Test注解: 測試是需要運(yùn)行來完成的。如果我們只有一個(gè)main方法,顯然在結(jié)構(gòu)上還是需要我們?nèi)プ⑨尩魷y試過的。解決此問題這里我們需要在測試方法上方加@Test注解來完成測試,只要是加該注解的方法,可以單獨(dú)運(yùn)行此方法來完成測試。
- IDEA快捷導(dǎo)入Junit4、5: 使用IDEA的小伙伴,你們的福音來了。我們可以先創(chuàng)建測試類和方法,然后在測試方法上方加入@Test注解,此時(shí)IDEA顯示的@Test注解是飄紅的,這時(shí)候我們使用Alt + Enter組合鍵來打開導(dǎo)入Junit單元測試列表,然后再選擇Junit4或者Junit5確定。
在SpringBoot往往存在單元測試用到如下的注解與寫法:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Transactional
@Rollback(true) // 事務(wù)自動(dòng)回滾,默認(rèn)是true??梢圆粚?/span>
public class NoticeServiceTest {
@Autowired
private NoticeService noticeService;
@Test
public void sayHello() {
helloService.sayHello("zhangsan");
}
}
在上面這個(gè)例子中,@SpringBootTest啟動(dòng)了SpringBoot環(huán)境,掃描應(yīng)用程序的spring配置,并構(gòu)建完整的Spring Context,其classes = Application.class啟動(dòng)了整個(gè)項(xiàng)目。通過@SpringBootTest我們可以指定啟動(dòng)類,或者給@SpringBootTest的參數(shù)webEnvironment賦值為SpringBootTest.WebEnvironment.RANDOM_PORT,這樣就會(huì)啟動(dòng)web容器,并監(jiān)聽一個(gè)隨機(jī)的端口,同時(shí),為我們自動(dòng)裝配一個(gè)TestRestTemplate類型的bean來輔助我們發(fā)送測試請(qǐng)求。
@Transactional表明調(diào)用數(shù)據(jù)庫并作事務(wù)處理;@RunWith(SpringRunner.class)聲明在Spring的環(huán)境中進(jìn)行單元測試,這樣Spring的相關(guān)注解就會(huì)被識(shí)別并起效,而@Autowired啟動(dòng)了Spring。
當(dāng)項(xiàng)目使用了@Component注解,在SpringBoot項(xiàng)目啟動(dòng)的時(shí)候就會(huì)跟著實(shí)例化/啟動(dòng),這個(gè)@Component注解的類里有多線程方法,隨著啟動(dòng)類中定義的ApplicationStartup類啟動(dòng)了,那么在你執(zhí)行單元測試的時(shí)候,由于多線程任務(wù)的影響,就可能對(duì)你的數(shù)據(jù)庫造成了數(shù)據(jù)修改,即使你使用了事務(wù)回滾注解。
優(yōu)化
高效的單元測試應(yīng)該脫離數(shù)據(jù)庫,以滿足快速啟動(dòng)完成測試、支持服務(wù)間調(diào)用的需求??梢酝ㄟ^如下幾點(diǎn)來對(duì)上述例子進(jìn)行優(yōu)化:
(1) 啟動(dòng)Spring會(huì)讓run->Junit Test的時(shí)候程序變慢,這是每次運(yùn)行單元測試都很慢的原因之一。然后單元測試是只針對(duì)某一個(gè)類的方法來測,啟動(dòng)Spring很多時(shí)候是多余的,所以我們只需要對(duì)應(yīng)的實(shí)體類實(shí)例就夠了。在需要注入bean的時(shí)候,我們直接new。
private NoticeService noticeService = new NoticeService();
(2) @SpringBootTest是在SpringBoot項(xiàng)目上使用的,它在@SpringBootContextLoader的基礎(chǔ)上,配置文件屬性的讀取,會(huì)讀取、解析一些項(xiàng)目配置文件,還會(huì)連接數(shù)據(jù)庫,然后如果啟動(dòng)類又帶有別的啟動(dòng)類、@Component、多線程等,而單元測試很多時(shí)候可以避免啟動(dòng)SpringBoot,減少啟動(dòng)所耗費(fèi)的大量時(shí)間,即不使用@SpringBootTest注解。
(3) 應(yīng)當(dāng)使用斷言來判斷單元測試結(jié)果是否符合預(yù)期。
(4) @RunWith 在JUnit中有很多個(gè)Runner,他們負(fù)責(zé)調(diào)用具體測試代碼,每一個(gè)Runner都有各自的特殊功能,你要根據(jù)需要選擇不同的Runner來運(yùn)行你的測試代碼,且一般都是使用SpringRunner.class。如果我們只是簡單的做普通Java測試,不涉及Spring Web項(xiàng)目,可以省略@RunWith注解,這樣系統(tǒng)會(huì)自動(dòng)使用默認(rèn)Runner來運(yùn)行你的代碼。
(5) 單元測試可以通過Mock數(shù)據(jù)的方式避開對(duì)數(shù)據(jù)庫的調(diào)用,減少很多數(shù)據(jù)庫連接的時(shí)間。Mock是模擬一切操作數(shù)據(jù)庫的步驟,不執(zhí)行任何SQL,我們直接模擬這句操作數(shù)據(jù)庫的代碼執(zhí)行會(huì)是成功的,而且可以模擬任何返回值,主要有兩個(gè)注解。只要是本地的,自己寫的bean,都可以使用@MockBean,它會(huì)把所有操作數(shù)據(jù)庫的方法模擬。如果是沒有返回值的方法,我們就可以不管。如果是有返回值的方法,我們可以給它返回各自我們需要模擬的值。如果是我們本地,調(diào)用別的公司,別的地方給我們寫好的接口,不是操作我們自己的數(shù)據(jù)庫,是我們寫好入?yún)?,別人給我們返回值,我們就用@SpyBean。
Mock所需依賴如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
通過以上優(yōu)化,可以大大縮短我們單測的時(shí)間,提高我們開發(fā)效率。?