文章摘要:追求代碼質(zhì)量一直都是優(yōu)秀程序員對(duì)自己的目標(biāo),那么有什么好方法能夠?qū)崿F(xiàn)這個(gè)目標(biāo)?
在每個(gè)系統(tǒng)上線正式發(fā)布之前,開發(fā)同事對(duì)其中功能點(diǎn)進(jìn)行自測,測試同事根據(jù)前期設(shè)計(jì)的測試用例進(jìn)行功能測試的都是保障系統(tǒng)可靠穩(wěn)定運(yùn)行的重要前提。但是,系統(tǒng)上線后故障還偶有發(fā)生,那么如何才能將系統(tǒng)代碼質(zhì)量提高一個(gè)檔次做到上線后0故障的目標(biāo)呢?我想這個(gè)問題一直是許多研發(fā)同學(xué)和測試同學(xué)共同追求的一個(gè)目標(biāo),但光靠代碼review、簡單的自測和功能測試用例覆蓋還是不夠,需要從代碼覆蓋率(包括語句覆蓋率、分支覆蓋率和路徑覆蓋率等)的角度來解決。因此,本文從解決問題的根本原因出發(fā)介紹以SpringBoot工程的自動(dòng)化單元測試用例結(jié)合Cobetura插件來實(shí)現(xiàn)定時(shí)跑測試任務(wù)并生成測試報(bào)告。
一、代碼質(zhì)量與單元測試
追求代碼質(zhì)量是一個(gè)優(yōu)秀程序員對(duì)自我的要求。我們寫一段代碼、一個(gè)方法和一個(gè)類,不僅僅說完成了編碼,保證代碼能正常得跑起來就行了,而且也必須使得代碼是優(yōu)雅和干凈的。一般來說正常的情況大家都能考慮到,比較關(guān)鍵和重要的是,我們?cè)趯懘a時(shí)除了能夠執(zhí)行正常業(yè)務(wù)邏輯以外,還要能考慮和覆蓋到各種不同的異常情況。我想在編碼時(shí)候,考慮正常和異常情況的時(shí)間分配比例應(yīng)該是30%:70%。
從主觀上來說,代碼質(zhì)量一般是跟程序員的專業(yè)技能熟練度,比如編程語言(C++/Java/go等)、技術(shù)框架(Spring/Dubbo/Spring Cloud/Spring Boot等)、設(shè)計(jì)模式(工廠/抽象/代理模式等),成正比的。但是,對(duì)于極為優(yōu)秀的程序員來說即使能夠盡可能地確保自己的千行代碼沒有缺陷,卻不一定能夠保證幾萬行都沒有任何缺陷。所以,我們需要借鑒其他的方法來提高自己的代碼質(zhì)量,盡可能少地讓潛在的問題暴露在生產(chǎn)環(huán)境上。
增加功能測試用例和接口單元測試都是能夠提高代碼質(zhì)量的方式,各有優(yōu)劣。本文從編程者的角度出發(fā),更加注重的是代碼覆蓋測試,畢竟只有寫代碼的人才能更容易地把控代碼中的業(yè)務(wù)邏輯,能夠更好的編寫單元測試用例以覆蓋正常和異常的業(yè)務(wù)場景。
在做單元接口測試時(shí),代碼覆蓋率常常是被拿來作為衡量測試好壞的指標(biāo),甚至,用代碼覆蓋率來考核測試任務(wù)完成情況。通常來說,我們會(huì)關(guān)注方法覆蓋、語句覆蓋、條件覆蓋和分支覆蓋這幾種度量方式。
二、Spring Boot工程的代碼單元測試
本文第一節(jié)主要都是講了理論,相對(duì)比較枯燥。下面這一節(jié)將從實(shí)踐的角度,來一步一步向大家展示如何在Spring Boot工程中對(duì)業(yè)務(wù)代碼寫單元測試用例。
1、版本環(huán)境
Spring Boot 1.4.1.RELEASE、JDK1.8
2、Spring Boot工程引入單元測試
在Spring Boot工程中引入單元測試比較簡單,只需要簡單地在pom文件中引入依賴如下:

在工程中引入spring-boot-starter-test后,就會(huì)有如下幾個(gè)庫:
(a)JUnit:Java語言的單元測試框架;
(b)SpringTest & Spring Boot Test:為Spring Boot程序提供集成測試的工具;
(c)AssertJ:?一種斷言庫;
(d)Hamcrest:也是一種斷言庫,不過更新頻度較慢;
(e)Mockito?:Java程序Mock測試的框架;
(f)JsonPath?:Xpath在Json中的應(yīng)用庫;
(g)JSONassert:Json的斷言庫;
spring-boot-starter-test的pom依賴圖如下:

3、工程中Service/Dao的單元測試
對(duì)于Spring Boot工程中的Service/Dao層的類來說,創(chuàng)建其單元測試方法比較簡單,直接手動(dòng)創(chuàng)建即可。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes? = OpHuaweiAgentApplication.class)
@Slf4j
public? class VmServiceTest {
??? private ObjectMapper mapper = new? ObjectMapper();
??? private static final String? JSON_CREATE_VM_REQUEST = "/vm/createVmReq.json";
??? @Autowired
? private VmService vmService;
?? ?@Before
??? public void setUp() throws Exception {
??????? //測試用例數(shù)據(jù)準(zhǔn)備
?????????????????? //code here
??? }
??? @Test
??? public void create() throws Exception {
??????? String createBody =? getResource(JSON_CREATE_VM_REQUEST);
??????? JSONObject body =? JSONObject.parseObject(createBody);
??????? HwTokenWrapper token =? getDomainToken();
??????? String projectId =? token.getToken().getProject().getId();
??????? CreateJobRespDto respDto =? vmService.create(token.getId(), projectId, body);
??????? assertTrue(null !=? respDto.getJobId());
??????? log.debug("create vm job創(chuàng)建成功,jobId:{}",? respDto);
??? }
???????? @After
???????? public void cleanUp() throws? Exception(){
?????????????????? //測試數(shù)據(jù)清理
?????????????????? //code here
???????? }
如上面的對(duì)Service層的單元測試用例代碼可見,在帶有@Before注解的方法setUp中完成對(duì)測試用例的數(shù)據(jù)準(zhǔn)備,可以提前在測試環(huán)境數(shù)據(jù)庫中插入測試用例所需依賴的測試局?jǐn)?shù)據(jù)。在@Test注解的方法—create是單元測試真正執(zhí)行的方法,示例中使用提前組織好的創(chuàng)建主機(jī)規(guī)格的Json數(shù)據(jù)作為參數(shù)調(diào)用被測試的Service層的VmService方法,執(zhí)行創(chuàng)建主機(jī)的驗(yàn)證。同時(shí)使用斷言機(jī)制,來判斷返回結(jié)果是否跟預(yù)期的一致。其中,準(zhǔn)備好的Json數(shù)據(jù)放在SpringBoot工程的src/test/resources下面。最后在,@After注解的方法cleanUp下執(zhí)行提前插入數(shù)據(jù)的回滾和清理。
4、工程中Controller Api的單元測試
對(duì)Service/Dao層的類進(jìn)行接口單元測試還是比較簡便的。然而,一般的SpringBoot工程都需要對(duì)外部提供Api接口,因此有必要對(duì)Controller層進(jìn)行單元測試以保證控制器執(zhí)行的業(yè)務(wù)邏輯正確,這時(shí)候就得用到MockMvc了。使用MockMvc可以使得開發(fā)或者測試不必再借助postman這種Http調(diào)試工具進(jìn)行手動(dòng)測試,既提高測試的效率,也能夠反復(fù)跑單元測試用例來進(jìn)行回歸驗(yàn)證。
Spring Test框架中的MockMvc實(shí)現(xiàn)了對(duì)Http請(qǐng)求的模擬,能夠直接通過網(wǎng)絡(luò)的形式,轉(zhuǎn)換到Controller層的Api調(diào)用,這樣在提高測試效率的同時(shí)可以不依賴外部環(huán)境。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes? = OpHuaweiAgentApplication.class)
@WebAppConfiguration
@Slf4j
public? class OrderManageControllerTest extends AbstractTest {
???????? //此處API URL為示例代碼
??? public static final String? GENERATE_ORDERID_API_URL = "/rest/xxxxxxxxx";
???????? //此處為鑒權(quán)的Json測試數(shù)據(jù)
???????? private static final String? JSON_AUTH_TOKEN_REQ = "/api/order/authTokenReq.json";
??? @Autowired
??? private WebApplicationContext context;
??? private MockMvc mvc;
??? @Before
??? public void setUp() {
?????????????????? //測試用例數(shù)據(jù)準(zhǔn)備
?????????????????? //code here
??????? this.mvc =? MockMvcBuilders.webAppContextSetup(this.context).build();
??? }
??? @Test
??? public void generateOrderIdTest() throws? Exception {
??????? JSONObject jsonObject = JSONObject.parseObject(getResource(JSON_AUTH_TOKEN_REQ));
??????? MockHttpServletRequestBuilder builder? = post(GENERATE_ORDERID_API_URL). header("X-Auth-Token",? jsonObject.getString("token"));
MvcResult result =? mvc.perform(builder).andReturn();
assertEquals(HttpStatus.OK.value(),? result.getResponse().getStatus());
}
???????? @After
???????? public void cleanUp() throws? Exception(){
?????????????????? //測試數(shù)據(jù)清理
?????????????????? //code here
???????? }
從上面對(duì)Controller層Api接口的單元測試示例代碼可見,在帶有@Before注解的setUp方法中,通過MockMvcBuilders工具類使用注入的WebApplicationContext上下文對(duì)象創(chuàng)建MockMvc對(duì)象。這里,MockMvc對(duì)象提供一組工具函數(shù)用來執(zhí)行assert判斷,都是針對(duì)web請(qǐng)求的判斷。這組工具的使用方式是函數(shù)的鏈?zhǔn)秸{(diào)用,允許程序員將多個(gè)測試用例鏈接在一起,并進(jìn)行多個(gè)判斷。在帶有@Test注解的generateOrderIdTest測試方法中,先加載提前準(zhǔn)備好的鑒權(quán)請(qǐng)求JsonObject對(duì)象,然后MockMvc對(duì)象執(zhí)行相應(yīng)的post請(qǐng)求,其中參數(shù)為帶有Header頭的MockHttpServletRequestBuilder對(duì)象。最后,通過assertEquals斷言機(jī)制來確認(rèn)接口返回是否為Http響應(yīng)的正確編碼(200)。如同之前的一樣,@After注解的方法cleanUp下執(zhí)行提前插入數(shù)據(jù)的回滾和清理。
三、Spring Boot工程集成Cobetura插件
通過上面的內(nèi)容,可以在Spring Boot工程中完成對(duì)Controller/Service/Dao層的添加單元測試用例,但僅限于此只能通過單元測試用例的結(jié)果(是否跑成功)來判斷用例正確與否,而無法來判斷測試的其他度量指標(biāo),比如本文前面提到的方法覆蓋、語句覆蓋、條件覆蓋和分支覆蓋等。因此,這節(jié)通過引入第三方組件—Cobertura來完成這一目標(biāo)。
Cobertura 是一種開源的代碼覆蓋率檢測工具,它通過檢測基本的代碼,并觀察在測試包運(yùn)行時(shí)執(zhí)行了哪些代碼和沒有執(zhí)行哪些代碼,并最終以html或者xml的格式來呈現(xiàn)最終測試的度量指標(biāo)結(jié)果(比如分支覆蓋率和代碼行覆蓋率)。
1、Spring Boot工程的pom文件中添加Cobertura插件
在Spring Boot工程的pom文件中添加Cobertuar插件的配置如下:


2、運(yùn)行Coberuta插件生成測試報(bào)告
在Spring Boot工程目錄下執(zhí)行以下maven命令—“mvn cobertura:cobertura”,執(zhí)行完后會(huì)在target目錄里找到site目錄,用瀏覽器打開里面的index.html,這就是測試用例執(zhí)行完后cobertura-maven-plugin得出的覆蓋率報(bào)告。如圖下所示:


四、Cobertura與自動(dòng)化構(gòu)建工具Jenkins的集成
僅在本地對(duì)Spring Boot工程執(zhí)行Cobertura的maven命令,并不能很好的實(shí)現(xiàn)自動(dòng)持續(xù)集成的目標(biāo)。這一節(jié)主要將介紹如何在Jenkins工具中一步步集成Cobertura插件并完成Spring Boot工程的代碼覆蓋率測試報(bào)告輸出。
1、首先需要在Jenkins工具上完成Cobertura插件的安裝。

2、配置jenkins工具,修改maven的執(zhí)行命令,這里主要是添加cobertura執(zhí)行命令clean cobertura:cobertura package。

3、在Add post build action這個(gè)配置項(xiàng)中選擇如下Publish Cobertura Coverage Report:

4、這一步中需要選擇一個(gè)配置項(xiàng),該配置項(xiàng)目是最終cobertura生成xml/html report的路徑,在示例中的路徑為**/target/site/cobertura/coverage.xml。

5、最后,重新build該項(xiàng)目,即可在項(xiàng)目中看到本工程代碼覆蓋率的測試用例報(bào)告了:


五、總結(jié)
本文從代碼質(zhì)量與單元測試用例方面切入,先介紹了如何在Spring Boot工程中完成各層(Controller Api/Service/Dao層)的接口單元白盒測試,隨后介紹了如何在Spring Boot工程中集成Cobertura插件,并利用Jenkins工具進(jìn)行自動(dòng)化持續(xù)集成以產(chǎn)生代碼覆蓋率的測試報(bào)告。限于筆者的才疏學(xué)淺,對(duì)本文內(nèi)容可能還有理解不到位的地方,如有闡述不合理之處還望留言一起探討。
個(gè)人專屬的公眾號(hào)(匠心獨(dú)運(yùn)的博客),歡迎關(guān)注一起交流和學(xué)習(xí)二維碼如下:
