大將軍手把手教你:Spring boot單元測試(二)

大將軍手把手教你:Spring boot單元測試(一)中講了spring boot開發環境的搭建

一.爲何要做單元測試

二.單元測試的常用步驟

1.拿到代碼之後,先把路徑圖畫出來

2.設計測試用例,利用黑盒,白盒設計測試用例,等價類,路徑覆蓋,條件組合等方式設計測試用例。

3.根據測試用例進行單元測試的編碼。

三.實戰

1.現在spring boot插件

打開idea-Configure-plugins,搜索Spring Assistant,然後點擊安裝install,安裝後重啓IDE

2.新建項目選擇Spring Assistant,server選擇默認,點擊next。

自定義項目信息,我這裏修改了project name

點擊下一步,選擇項目類型,這裏可以選擇web,其實其他類型也可以。


這裏明明已經選擇了項目名稱了,又讓輸入了一個項目名稱,這裏的項目是可以有多個的。

看下項目結構,你會發現src下main和test兩個目錄的文件夾結構基本是一致的,只是test的文件比main的文件名中多了一個test

這裏main和test是一一對應的,大家在新建測試類的時候,要放在test文件夾下,報名就是功能類+test.java

repository         類中有自帶的集成的方法有很多,不建議大家每個都進行單元測試,如果自己在repository中寫的有自己的方法,可以寫一下,如果只繼承,什麼都沒有寫的話是可以不測這個類的

如果需要測試需要怎麼測呢

package org.example.repository;

import org.example.entity.Info;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.transaction.Transactional;
import java.util.List;

@SpringBootTest ###裝飾器
@RunWith(SpringRunner.class)  ###一個容器
public class InfoRepositoryTest {

    private @Autowired InfoRepository repository; ###通過Autowired的方式把repository引入進來
###這裏before在測試前往數據庫增加幾條數據,類似py單元測試的setup
    public @Before void dataPrepare() {
        Info info = new Info();
        info.setId(2L);
        info.setOutput(2);
        repository.save(info);
    }
###這裏註解是test,測試findAll 這個方法,同時注意,測試結果不要print,一定要通過assert打印出來,選擇findAll右鍵運行,可查看到實際的結果
    public @Test void findAll() {
        List<Info> info = repository.findAll();
        Assert.assertEquals(1, info.size());
        Assert.assertNotNull(info.get(0));
        Assert.assertTrue(info.get(0).getId().equals(2L));
        Assert.assertTrue(info.get(0).getOutput().equals(2));
    }

    @Transactional
    public @Test void save() {
        Info info = new Info();
        info.setId(1L);
        info.setOutput(1);
        repository.save(info);
        Info r = repository.findOne(1L);
        Assert.assertEquals(2, repository.findAll().size());
        Assert.assertNotNull(r);
        Assert.assertTrue(r.getId().equals(1L));
        Assert.assertTrue(r.getOutput().equals(1));
        //repository.delete(1L);
    }
###結束後的處理在這裏,刪除了2行數據
    public @After void dataRemove() {
        repository.delete(2L);
    }
}

如果是查詢的測試,這樣就可以了,如果是插入數據,比如上段代碼中的save方法。

因爲在單元測試這個類是沒有事務控制的,相當於一句話就是一個事務,爲了保證插入的正確性Transactional,這樣這個方法就在一個事務裏了。這裏按照平常邏輯repository插入之後是要做一次刪除的,對吧,但是在測試的事務裏默認會對事務進行回滾,所以可以把repository.delete註釋掉,不需要考慮清理數據了,如果這裏不需要對事務進行回滾,可以@Rollback(false)

service   :是有邏輯的,測試跟repository一樣,包名保持一致,遵守命名規範就可以。在測試用例前後做好before和after的處理樣例中的腳本是一個存和一個查

package org.example.service;

import org.example.entity.Info;
import org.example.repository.InfoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class InfoService {

    private @Autowired InfoRepository repository;

    public List<Info> findAll() {
        return repository.findAll();
    }

    public Info save(Info info) {
        return repository.save(info);
    }
}

如果大家在執行測試的時候會遇到一些找不到測試類或者沒有測試匹配的情況,是因爲啓動類檢查下啓動位置不太對或者不是嚴格按照命名規範命名的。這是可以指定啓動類@SpringBootTest(classes={Application.class})

controller   測試,因爲這裏涉及的有邏輯,所以要先涉及測試用例,測試類的生成,定義在包名同包下就可以了。

 

建議大家用spring自帶的http進行測試,這時要引入裝飾器函數

@AutoConfigureMockMvc

@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc###需要加個裝飾器
public class InfoControllerTest {

    private @Autowired MockMvc mockMvc;###這裏直接導入mockMvc就可以了
    private @Autowired InfoRepository repository;
    private @Autowired ObjectMapper mapper;
    private List<List<String>> unit = new ArrayList<List<String>>(8) { 
    	private static final long serialVersionUID = -7765488012354542450L;
###以下就是我準備的測試用例

	{
    	add(Arrays.asList("2", "0", "4", "3"));
    	add(Arrays.asList("2", "1", "1", "2"));
    	add(Arrays.asList("1", "0", "3", "4"));
    	add(Arrays.asList("1", "1", "1", "1"));
    	add(Arrays.asList("3", "0", "3", "1"));
    	add(Arrays.asList("2147487630", "0", "4", ""));
    	add(Arrays.asList("a", "0", "4", ""));
    	add(Arrays.asList(null, "0", "4", ""));
    	
    } };
@Transactional
    public @Test void executeTest() {
    	unit.forEach(u -> {
    		try {
    			if (StringUtils.hasLength(u.get(3))) {
###這裏mvc提供的方法perform指定訪問的url
    				mockMvc.perform(MockMvcRequestBuilders.get("/?inputA=" + u.get(0) + "&inputB=" + u.get(1) + "&inputX=" + u.get(2)))
					   	   .andExpect(MockMvcResultMatchers.status().isOk())###斷言訪問是成功的
					   	   .andExpect(MockMvcResultMatchers.content().string(u.get(3)))###content的返回值是多少
					   	   .andDo(MockMvcResultHandlers.print());###這裏打印不是必須
    				return;
    			}
    			mockMvc.perform(MockMvcRequestBuilders.get("/?inputA=" + u.get(0) + "&inputB=" + u.get(1) + "&inputX=" + u.get(2)))
			   	       .andExpect(MockMvcResultMatchers.status().is4xxClientError())###因爲代碼邏輯是參數錯誤返回的是4XX,所以這裏用了4XX做了斷言
			   	       .andDo(MockMvcResultHandlers.print());
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	});

 

注意:如果一個類的方法比較多,這樣單個執行效率會很低,可以在class上右鍵執行類裏所有的方法。

如果項目比較大,每執行一個用例都要把起spring環境,速度就會比較慢,把@SpringBootTest換成

@WebMvcTest(controllers = { InfoController.class },  secure = false),把想測試的contrullers列進去。secure爲false則不測登錄,因爲controllers測試的時候只注入了controllers,沒有注入service,所以在調用的時候會報錯,這時候可以通過裝飾器 @MockBean InfoService service;模擬services的返回,在下邊測試的時候
BDDMockito.given(service.save(info)).willReturn(new Info());制定在調用services.save的時候會直接返回,這樣就可以單獨去測試某一個controller

如果某些測試是針對不同的測試文件,這裏可以指定某個配置文件。

@ActiveProfiles("pro")

如果被測試數據比較複雜,也不用每個字段都做斷言,可以用jsonPath進行斷言。

像我們公司的代碼要求代碼的單元測試覆蓋率達到百分之80,可以通過sonar進行掃描,sonar可以把你沒有覆蓋的行,類沒有覆蓋到,以及路徑沒有覆蓋。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章