目錄
1.指定測試标準
2.設計測試用例
3.測試集示例
4.跑測試集
1.指定測試标準
單元測試會用到mock和junit的内容,作者前文有詳解,可移步:
Spring Boot單元測試-CSDN博客
mockito的詳細使用-CSDN博客
1.1.測哪一層?
以當前後端标準的MVC分層來說,後端代碼分爲controller、service、dao三層。首先我們要先确定這三層裏面去測試哪一層?單元測試的核心目的是什麽:
覆蓋業務代碼
按标準的來說的話controller層是系統對外暴露的API,這一層級隻負責做一些請求和參數的處理;service層用來編寫具體的業務邏輯;dao層負責與數據庫進行交互。所以我們應該測service層。
1.2.如何判斷測試是否通過?
測試的輸出結果和我們期望的輸出結果是一緻的,測試就通過了。怎麽判斷喃?
用Assert斷言
Assert不要到處去用,在測試用例的最後用它來判斷一下輸出結果是不是期望值即可。
1.3.mock掉哪些内容?
mock我們主要拿來幹兩件事兒:
-
mock掉沒辦法達到的地方,比如有些地方不影響代碼邏輯,但是在測試的時候不好造出來,這些不可達的地方可以mock掉。
mock我們要mock兩種情況:
-
mock返回值
-
mock行爲
mock返回值,比如:
Train train = new Train(); String id = UUID.randomUUID() + ""; train.setKeyId(id); when(trainDao.getDetail(any(Train.class))).thenReturn(train);
mock行爲有些時候是主動的,我們想去定義實體的具體行爲,有時候是被動的,比如要mock的dao方法沒有返回值該,我們就隻能通過去mock行爲來使得它不去操作數據庫,反正核心就是不讓它去操作數據庫。
比如以下方法:
void trainDetailDao.updateList(XXX)
用doAnswer去mock它的響應:
@Test public void modifyTrainDetails(){ TrainDetailList trainDetails = new TrainDetailList(); TrainDetail trainDetail = new TrainDetail(); trainDetail.setKeyId(UUID.randomUUID()+""); trainDetails.add(trainDetail); doAnswer(invocation -> { List trainDetailList = (List) invocation.getArguments()[0]; Assert.assertEquals(trainDetails.getItems(), trainDetailList); return trainDetails; }).when(trainDetailDao).updateList(any()); trainDetailBaseSvr.modifyTrainDetails("",trainDetails); }
2.設計測試用例
一個接口隻需要一個測試用例嗎?有時候是不夠的。
衡量對一個接口的單元測試是不是到位了,核心指标是看它的分支覆蓋率。代碼種的一個方法裏面有些時候會存在一些選擇分支(帶判斷性質的語句),我們設計測試用例的時候要考慮覆蓋掉所有分支。
最好的辦法就是畫個流程圖,設計測試用例的時候要覆蓋掉所有流程分支,以下以用戶買豬肉爲一個例子:
灰色的節點就是要mock掉的
細化成流程圖,流程圖的所有出口就是要覆蓋的分支,有幾個出口,就應該有幾個用例,有幾個測試方法:
3.測試集示例
以下是作者在工作中編寫的一個測試集用例,演示了一個簡單的對增删改查方法的覆蓋。裏面演示了如何覆蓋有返回值的方法和沒有返回值的方法。
這裏有幾個技巧分享一下:
首先是要mock掉dao層的話,我們就要把service裏面依賴的dao換成mock出來的dao,這裏需要用反射的方式強行訪問到service裏面的dao,然後把它替換掉。其次mock掉dao層之後直接new service就行,完全不需要用到自動注入,也就是不需要用到IOC,也就不需要用到@RunWith(XXX.class) @SpringBootTest(classes = XXX.class)之類的注解來啓動SpringBoot了。這樣跑測試用例的時候,省去了啓動時間,會快很多。
public class ExaminationBaseSvrTest extends PropertyControllerBase { IExaminationBaseSvr examinationBaseSvr; ExaminationTargetService examinationTargetService; private ExaminationDao examinationDao; private IDataDicItemBaseMgeSvr dataDicItemBaseMgeSvr; private DataDictionaryItemDao dataDictionaryItemDao; @Before public void setUp() throws Exception{ examinationBaseSvr = new ExaminationBaseSvr(); Field field = ExaminationBaseSvr.class.getDeclaredField("examinationDao"); Field dataDicItemBaseMgeSvrField = ExaminationBaseSvr.class.getDeclaredField("dataDicItemBaseMgeSvr"); Field dataDictionaryItemDaoField = DataDicItemBaseMgeSvr.class.getDeclaredField("dataDictionaryItemDao"); field.setAccessible(true); dataDicItemBaseMgeSvrField.setAccessible(true); dataDictionaryItemDaoField.setAccessible(true); examinationDao = mock(ExaminationDao.class); dataDictionaryItemDao=mock(DataDictionaryItemDao.class); dataDicItemBaseMgeSvr=mock(DataDicItemBaseMgeSvr.class); field.set(examinationBaseSvr, examinationDao); dataDicItemBaseMgeSvrField.set(examinationBaseSvr,dataDicItemBaseMgeSvr); dataDictionaryItemDaoField.set(dataDicItemBaseMgeSvr,dataDictionaryItemDao); } @Test public void addExamination(){ when(examinationDao.insert(any())).thenReturn(1); Examination examination = new Examination(); examination.setKeyId(UUID.randomUUID()+""); Assert.assertEquals(examinationBaseSvr.addExamination("",examination),examination); } @Test public void addExcaminations(){ ExaminationList examinations = new ExaminationList(); Examination examination = new Examination(); examination.setKeyId(UUID.randomUUID()+""); examinations.add(examination); doAnswer(invocation -> { List examinationList = (List)invocation.getArguments()[0]; Assert.assertEquals(examinationList,examinations.getItems()); return 1; }).when(examinationDao).insertList(any()); examinationBaseSvr.addExaminations("",examinations); } @Test public void modifyExamination(){ when(examinationDao.update(any())).thenReturn(1); Examination examination = new Examination(); examination.setKeyId(UUID.randomUUID()+""); Assert.assertEquals(examinationBaseSvr.modifyExamination("",examination),examination); } @Test public void modifyExaminations(){ ExaminationList examinations = new ExaminationList(); Examination examination = new Examination(); examination.setKeyId(UUID.randomUUID()+""); examinations.add(examination); doAnswer(invocation -> { List examinationList = (List)invocation.getArguments()[0]; Assert.assertEquals(examinationList,examinations.getItems()); return 1; }).when(examinationDao).updateList(examinations.getItems()); examinationBaseSvr.modifyExaminations("",examinations); } @Test public void deleteExamination(){ Examination examination = new Examination(); examination.setKeyId(UUID.randomUUID()+""); when(examinationDao.update(any())).thenReturn(1); when(examinationDao.getDetail(any())).thenReturn(examination); Assert.assertEquals(examinationBaseSvr.deleteExamination("",examination.getKeyId(),false),1); } @Test public void deleteExaminations(){ ExaminationList examinations = new ExaminationList(); Examination examination = new Examination(); examination.setKeyId(UUID.randomUUID()+""); examinations.add(examination); doAnswer(invocation -> { List examinationList = (List)invocation.getArguments()[0]; Assert.assertEquals(examinationList,examinations.getItems()); return 1; }).when(examinationDao).updateList(any()); examinationBaseSvr.deleteExamination("",examinations,false); } }
4.跑測試集
測試類寫完之後,類名旁邊有一個run的圖标,點擊即可跑整個測試集。其中有普通的run以及帶覆蓋率報告的run:
選擇帶覆蓋率的run之後會顯示覆蓋率:
相看類裏面具體是哪些代碼段被覆蓋了,可以在跑完測試集後進入具體的被測試類,代碼行旁邊會有顔色條,綠色表示被cover的内容:
-