【SpringBoot】單元測試實戰演示及心得分享

慈雲數據 6個月前 (05-30) 技術支持 46 0

目錄

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掉dao層的方法

  • 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的内容:

微信掃一掃加客服

微信掃一掃加客服

點擊啓動AI問答
Draggable Icon