亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享
原(yuan)創

基于SpringBoot實現單元測試的多種情境/方法(一)

2022-12-05 03:35:45
137
0

1 什么是單元測試(UT)?

最近(jin)工作(zuo)涉(she)及到給一(yi)些代碼(ma)(ma)寫單(dan)(dan)元測試,這(zhe)可謂(wei)是功能上線(xian)前評判代碼(ma)(ma)質(zhi)量(liang)極(ji)其重要的(de)一(yi)步,當然(ran)單(dan)(dan)元測試之后還有(you)系(xi)統測試、集成(cheng)測試等,這(zhe)些測試的(de)關系(xi)和意義不是本文的(de)重點內容,并且比(bi)較(jiao)好理解,這(zhe)里貼(tie)一(yi)個鏈接大家(jia)可以(yi)自行按需閱讀://blog.csdn.net/u013185349/article/details/123396943

總的來看單元(yuan)測試有三個核心要點:

1.測試范圍:代碼中的每一個小的單元(一般是類/方法)
單元(yuan)測(ce)(ce)試不測(ce)(ce)試模塊或系統。一般都(dou)是(shi)(shi)由開(kai)發(fa)工程(cheng)師而(er)非(fei)測(ce)(ce)試工程(cheng)師完(wan)成(cheng)的,是(shi)(shi)代碼(ma)層(ceng)面的測(ce)(ce)試,用于測(ce)(ce)試“自(zi)己”編寫的代碼(ma)的正確性。

2.測試依賴:不依賴于任何不可控組建
單元測試不依賴(lai)于任何不可(ke)控的組件(jian),即使代碼中依賴(lai)了其(qi)他這些(xie)不可(ke)控的組件(jian)(比如復(fu)雜(za)外部系統,復(fu)雜(za)模塊或類(lei)),也需(xu)要通(tong)過(guo) mock 的方式將其(qi)變成(cheng)可(ke)控。

3.測試意義:得到預期的輸入輸出
在你寫完一個功(gong)能代碼之后,怎(zen)么(me)保證你的(de)代碼運(yun)行正確(que)?在各種異(yi)(yi)常(數據異(yi)(yi)常、輸入異(yi)(yi)常、調用(yong)異(yi)(yi)常等(deng))情況下(xia),程序運(yun)行結果都符合(he)你預先設計的(de)預期,返回(hui)合(he)適的(de)報錯呢?這(zhe)個時(shi)候,單(dan)元測試(shi)就派上了用(yong)場(chang)

2 單元測試的工具

常用的單元測試(shi)工(gong)具有三種(zhong),在這里我們(men)都會(hui)介紹,從(cong)基(ji)礎到進階的用法慢(man)(man)慢(man)(man)都會(hui)寫到

JUnit

經典中的經典,相信哪怕是入門java的學習者,絕大部分也使用過JUnit的@Test注解來進行單元測試,但目前已經更新到JUnit5,很多屬性是有變化的,SpringBoot默認(ren)集(ji)成(cheng)的也是(shi)JUnit5版本,所以(yi)本文(wen)可能(neng)會與一些網(wang)上能(neng)找到的資料有所不同

Asserts

就(jiu)是我(wo)們常說的“斷言”,也(ye)是非常簡單好(hao)用(yong)的工具(ju),功能強悍,支持各種斷言方法

Mockito

Mock可能對于部分初學者來說比較陌生,所以這里多說一些:JUnit固然很簡單方便,但如果在單元測試的過程中嗎,需要構建請求頭和查詢參數、構建復雜的腳本等特殊需求,用標準的JUnit是無法實現的,這個時候我們就要用到MockMVC

什么是mock?

即mock object,模擬對象,是在OOP中模擬真實對象行為的假對象,在單元測試中有時無法使用真實對象,因此需要用到模擬對象來測試。

比如在以下情況可以采用模擬對象來替代真實對象:
1.真實對象的行為是不確定的 (例如,當前的時間或溫度) ;
2.真實對象很難搭建起來;
3.真實對象的行為很難觸發 (例如,網絡錯誤) ;
4.真實對象速度很慢(例如,一個完整的數據庫,在測試之前可能需要初始化) ;
5.真實的對象是用戶界面, 或包括用戶界面在內;
6.真實的對象使用了回調機制:
7.真實對象可能還不存在:
8.真實(shi)對象可能包含不能用(yong)作測試(而不是為實(shi)際工作)的信息和方法(fa)。

舉例:只做了一點簡單的更改,但是驗證需要重啟底層資源,一等就是五六分鐘;要模擬在某個操作系統/瀏覽器環境下的功能表現,總不能專門去搭一套環境吧

因此spring給我們提供了Mockito工具,即模擬對象的生成器,開發分為三個步驟:
1.模擬外部依賴,比如我們需要的底層數據、網絡請求頭等
2.執行具體的測試代碼
3.驗證(zheng)產生的結果(guo)與(yu)預期是(shi)否相符(fu)

spring-test包提供(gong)了一(yi)個核心(xin)的(de)對象——MockMVC,MockMvc是(shi)由spring-test包提供(gong),實現了對Htp請求的(de)模擬(ni),能夠(gou)直(zhi)接使用網絡(luo)的(de)形式,轉換(huan)到(dao)Controller的(de)調用,使得測試速度(du)快.不(bu)依(yi)賴(lai)網絡(luo)環境(jing)。同時提供(gong)了一(yi)套驗證的(de)工具, 結果(guo)的(de)驗證十分方便。

3 編寫單元測試的原則

一(yi)般來說,單元(yuan)測試需要遵循3A模式(shi)(Arrange/Act/Assert)來編(bian)寫(xie)具(ju)(ju)體(ti)的代(dai)碼,具(ju)(ju)體(ti)的概(gai)念可(ke)以(yi)參考://juejin.cn/post/7005448543192252423

實際上3A是一個簡單實用(yong)的概念,舉個簡單的例(li)子:

Arrange:創建測試所需要的實例

Act:運行你所需要測試的具體行動(方法)

Assert:判斷返回的是否與預期一樣

4 Junit 5 實現基礎單元測試

1.建立相應的包存儲測試代碼
實際(ji)上Spring Boot對Junit進行了(le)整合,我們(men)可(ke)以(yi)從工(gong)程(cheng)架(jia)構中看(kan)到,創(chuang)建一個(ge)(ge)spring工(gong)程(cheng)后(springinitializer),會(hui)自帶(dai)一個(ge)(ge)src下的(de)(de)(de)test包和一個(ge)(ge)默(mo)認測(ce)試的(de)(de)(de)代(dai)碼(ma)(如下圖),如果沒(mei)有的(de)(de)(de)話(hua),也可(ke)以(yi)自己創(chuang)建一個(ge)(ge)(因為有些開發者(zhe)可(ke)能創(chuang)建的(de)(de)(de)是maven工(gong)程(cheng))

2.引入依賴
當然要想使用這個測試,我們當然要引入相關的依賴,一般來說只要你創建了springboot項目,它是自動導入的。但是需要注意的是,我們通常需要排除junit-vintage-engine這個依賴,為什么呢?
如果不排除這個依賴,用(yong)(yong)的不會(hui)是(shi)(shi)spring整合的Junit,從(cong)而后(hou)面寫測試代(dai)碼的時候(hou)可能需要加(jia)RunWith注解來指(zhi)定是(shi)(shi)用(yong)(yong)哪一個Spring整合的Junit

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <exclusions>
    <exclusion>
      <groupId>org.junit.vintage</groupId>
      <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
  </exclusions>
</dependency>

3.編寫測試代碼
這(zhe)里我們可以參考給我們的(de)默認測(ce)試代碼來寫,默認代碼會(hui)是這(zhe)種格式(@SpringBootTest就(jiu)是spring整合了junit之(zhi)后的(de)一(yi)個(ge)注解):

package wy.springboot01;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot01ApplicationTests {

    @Test
    void contextLoads() {
    }

}

我(wo)們(men)這里簡單寫一下,新建一個(ge)User類,來(lai)讓測試代碼輸出User的某個(ge)參數

package wy.springboot01.domain;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

/**
 * springboot項目啟動的時候,自動將application.yml內容加載到實體對象中
 */

@Data
//將實體類交給spring管理,自動掃描
@Component
public class User {
    private Integer uid;
    private String uname;
    private String password;
    private ArrayList<String> addrs;

    public User() {
    }

    public User(Integer uid, String uname, String password, ArrayList<String> addrs) {
        this.uid = uid;
        this.uname = uname;
        this.password = password;
        this.addrs = addrs;
    }
}

單元測試代碼:

package wy.springboot01;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import wy.springboot01.domain.User;

@SpringBootTest(classes = {User.class})
class Springboot01ApplicationTests {

    @Autowired
    private User user;
    @Test
    void contextLoads() {
        System.out.println(user.getAddrs());
    }

}

4.直接運行測試代碼,可能會失敗,常見錯誤是:Found multiple @SpringBootConfiguration annotated classes,這是因為之前在做其他代碼的時候,可能有多個文件用了@SpringBootConfiguration從而導致沖突,只需要注釋掉多余的只留一個即可,或者指定你要用哪個@SpringBootConfiguration來測試,因此我們這里用了:@SpringBootTest(classes = {User.class})

5.正常會輸出null,因(yin)為我(wo)們(men)并沒(mei)有(you)給Addrs這個參(can)數賦默認值(zhi),到這里我(wo)們(men)就完成了一個沒(mei)有(you)3A的最后(hou)一個A(斷(duan)言)的單元測試(shi)

6.但是我們在開發中通常不會只測試某一個類的參數,比如我們想測試在某個啟動類中(某個功能加載的過程中)User對象的值是否載入成功,那么也可以按如下來實現:
首先我們先寫一個yml文件來給User賦默(mo)認值,就比如叫做application.yml

user:
  uid: 1998051110
  uname: wuyu
  password: wuyu1999
  addrs:
    - Beijing
    - Sichuan
    - Nanchang

然后給User類加上注解:

//加載配置內容,設定配置前綴,注意:prefix參數不支持小駝峰原則,必須全部小寫
@ConfigurationProperties(prefix = "user")

最后把(ba)測試的啟動類(lei)(lei)改(gai)成這(zhe)個工程(cheng)的默認啟動類(lei)(lei):

@SpringBootTest(classes = {Springboot01Application.class})

這樣啟動之后我們就(jiu)會得到如(ru)下結果,默認值加(jia)載成功(gong)了:

[Beijing, Sichuan, Nanchang]

7.那么如何給結果做一個斷言呢?換而言之我不需要用人眼去判斷結果是不是對的,系統就西東判斷了,也很簡單,這里我們就要介紹到我們的第二種測試工具:asserts

5 Asserts方法判斷返回值

實際(ji)上我們在日(ri)常寫代碼(ma)中有時候也會用到assert,比如判(pan)斷某個(ge)入參是否為(wei)空,直接使用:assert data.length != 0; 即可

那么回到(dao)單元測試(shi),我們(men)如(ru)何判斷返回值?

//判斷方法返回是否為Flase
Assertions.assertFalse(SomeClass.someMethod());
//判斷方法返回是否為True
Assertions.assertTure(SomeClass.someMethod());
//判斷方法返回是否與對應值相等
Assertions.assertTure("預期返回", SomeClass.someMethod());

同樣將剛(gang)剛(gang)的Juint判(pan)斷user的例子擴充一(yi)(yi)下,我(wo)們判(pan)斷用戶名是否與預期一(yi)(yi)致,可以寫(xie)成:

@SpringBootTest(classes = {Springboot01Application.class})
class Springboot01ApplicationTests {

    @Autowired
    private User user;
    @Test
    void contextLoads() {
        Assertions.assertArrayEquals(String.join("","Beijing", "Sichuan", "Nanchang" ).toCharArray()
                ,String.join("",user.getAddrs() ).toCharArray());
    }

}

最(zui)終運行結(jie)果(guo)如下圖,就實現(xian)了不由人眼(yan)判斷(duan)而是系(xi)統判斷(duan),非常方便:

6 MockMVC

剛剛說過,mock就是用來模擬一些不太好去創建的代碼外部依賴,最典型的就是對于controller的測試,傳統的方法是代碼起來之后使用postman,而單元測試要求我們不能有這種外部依賴,因此我們就用mock來模擬這樣的一個環境

還是(shi)(shi)拿user類來(lai)舉例,比如(ru)我們有一個(ge)controller長這(zhe)個(ge)樣(yang)子,如(ru)果我們要驗證他的(de)(de)(de)返回是(shi)(shi)不是(shi)(shi)對(dui)的(de)(de)(de),按照常(chang)理我們應該(gai)去啟動spring并且用(yong)瀏覽器、postman、http腳(jiao)本等(deng)來(lai)看(kan)看(kan)它的(de)(de)(de)返回是(shi)(shi)什么,非常(chang)的(de)(de)(de)浪費(fei)時間

@GetMapping("/user")
public ArrayList<User> getUser(){
    System.out.println("user get......");
    ArrayList<User> users = new ArrayList<>();
    users.add(new User(1001,"wu","1212",new ArrayList<>(Arrays.asList("nanchang", "sichuan", "beijing"))));
    users.add(new User(1002,"du","1313",new ArrayList<>(Arrays.asList("chang", "sica", "beng"))));
    return users;
}

那么用mock怎么去做呢?
首先我們先在test包下,創建(jian)一個與(yu)默(mo)認測試代碼平行的測試類,比如(ru)就(jiu)叫MockMVCTester,如(ru)下:

package wy.springboot01;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import javax.annotation.Resource;
//進行每一次mock模擬tomcat容器的時候,使用隨機端口啟動,這樣不會有端口占用的問題
@SpringBootTest(classes = {Springboot01Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//自動配置以及啟用mvc對象
@AutoConfigureMockMvc
public class MockMVCTester {
    //注入MockMVC對象,它是springtest依賴中自帶的
    @Resource
    private MockMvc mockMvc;
    @Test
    public void testMock() throws Exception {
        //獲取mock返回的對象
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/user"))//perform模擬一個http請求,這里是get方法
                .andExpect(MockMvcResultMatchers.status().isOk())//添加預期,如果服務器返回的是200
                .andDo(MockMvcResultHandlers.print())//那我們就把請求和響應的信息在控制臺中打印輸出
                .andReturn();//將結果返回出來
    }
}

啟動測試方(fang)法testMock,可以發現:

同時(shi)也會返回controller的信息,可以對照(zhao)是否符合預期,這樣我們就實現了不需要啟動瀏覽器、postman等即(ji)可測試controller

但我們還可以更加便利一些,直接在運行的時候就判斷它的返回是不是符合預期,本例的返回為:
[{"uid":1001,"uname":"wu","password":"1212","addrs":["nanchang","sichuan","beijing"]},{"uid":1002,"uname":"du","password":"1313","addrs":["chang","sica","beng"]}]
因此我們將測試(shi)代碼(ma)做一些改動

package wy.springboot01;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import javax.annotation.Resource;
//進行每一次mock模擬tomcat容器的時候,使用隨機端口啟動,這樣不會有端口占用的問題
@SpringBootTest(classes = {Springboot01Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//自動配置以及啟用mvc對象
@AutoConfigureMockMvc
public class MockMVCTester {
    //注入MockMVC對象,它是springtest依賴中自帶的
    @Resource
    private MockMvc mockMvc;
    @Test
    public void testMock() throws Exception {
        //獲取mock返回的對象
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/user"))//perform模擬一個http請求,這里是get方法
                .andExpect(MockMvcResultMatchers.status().isOk())//添加預期,如果服務器返回的是200
                .andDo(MockMvcResultHandlers.print())//那我們就把請求和響應的信息在控制臺中打印輸出
                .andExpect(MockMvcResultMatchers.content().string("[{\"uid\":1001,\"uname\":\"wu\"," +
                        "\"password\":\"1212\",\"addrs\":[\"nanchang\",\"sichuan\",\"beijing\"]}," +
                        "{\"uid\":1002,\"uname\":\"du\",\"password\":\"1313\",\"addrs\"" +
                        ":[\"chang\",\"sica\",\"beng\"]}]"))//content表示對于返回的請求體數據進行判斷,string表示進行比對
                .andReturn();//將結果返回出來
    }
}

再次啟動,運行(xing)依然成功(gong)說明比(bi)對(dui)正確,返(fan)回符合預(yu)期,如果這個(ge)時候我(wo)們改了(le)controller的邏(luo)輯,則返(fan)回“test fail”,非常好(hao)用,甚(shen)至會比(bi)對(dui)預(yu)期值和實(shi)際值

注:如果覺得testMock()看起來不舒服,可以在@Test下面加注解@DisplayName("get方法測試用例"),來自定義測試方法名稱
如果有入參(can)、且(qie)返回是一(yi)個json可以參(can)考如下(xia)案例:

@Test
@DisplayName("get方法+有入參+有json返回")
public void testMock1() throws Exception {
    //mock返回的對象可以不獲取,因為單純的判斷對錯用不上
    mockMvc.perform(MockMvcRequestBuilders.get("/user/para")//perform模擬一個http請求,這里是get方法
                    .header("token", "akakak")//請求頭
                    .param("id","wy")//請求參數
                    .param("password","asd"))//請求參數
            .andExpect(MockMvcResultMatchers.status().isOk())//添加預期,如果服務器回的是200
            .andDo(MockMvcResultHandlers.print())//那我們就把請求和響應的信息在控制臺中打印輸出
            .andExpect(MockMvcResultMatchers.jsonPath("ak").value("asd"))//獲取返回的json并核對對應的值是否一樣
            .andReturn();//將結果返回出來
}

如果方法為post,可以參考如下案例

@Test
@DisplayName("post方法測試用例")
public void testMock1() throws Exception {
    //IoC
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("IoC.xml");
    User user = context.getBean(User.class);
    ObjectMapper mapper = new ObjectMapper();
    user.setUname("wy");
    //mock返回的對象可以不獲取,因為單純的判斷對錯用不上
    mockMvc.perform(MockMvcRequestBuilders.post("/user")//perform模擬一個http請求,這里是get方法
                    .content(mapper.writeValueAsString(user))//用IoC建立一個User對象
                    .contentType(MediaType.APPLICATION_JSON_VALUE))//添加json類數據,轉化為入參
            .andExpect(MockMvcResultMatchers.status().isOk())//添加預期,如果服務器回的是200
            .andDo(MockMvcResultHandlers.print())//那我們就把請求和響應的信息在控制臺中打印輸出
            .andExpect(MockMvcResultMatchers.jsonPath("uname").value("wy"))//獲取返回的json并核對對應的值是否一樣
            .andReturn();//將結果返回出來
}
0條評論
0 / 1000
才開始學技術的小白
23文(wen)章數
2粉(fen)絲數
才開始學技術的小白
23 文章 | 2 粉絲
原(yuan)創

基于SpringBoot實現單元測試的多種情境/方法(一)

2022-12-05 03:35:45
137
0

1 什么是單元測試(UT)?

最近工作涉及(ji)到給一些(xie)代碼寫單(dan)元測(ce)試(shi)(shi)(shi),這可(ke)謂是功能上線前評判代碼質量極其重(zhong)要的一步,當然(ran)單(dan)元測(ce)試(shi)(shi)(shi)之后還有(you)系統測(ce)試(shi)(shi)(shi)、集(ji)成測(ce)試(shi)(shi)(shi)等,這些(xie)測(ce)試(shi)(shi)(shi)的關系和意義不是本文的重(zhong)點內容,并且比較好(hao)理解,這里貼一個鏈接(jie)大家可(ke)以自行按需閱(yue)讀(du)://blog.csdn.net/u013185349/article/details/123396943

總的來看單元測試有三個核心要點:

1.測試范圍:代碼中的每一個小的單元(一般是類/方法)
單元測試不測試模(mo)塊或系統。一般都(dou)是由開發工(gong)程師而非測試工(gong)程師完成的(de)(de),是代(dai)(dai)碼(ma)層面的(de)(de)測試,用于(yu)測試“自(zi)己”編寫(xie)的(de)(de)代(dai)(dai)碼(ma)的(de)(de)正確(que)性。

2.測試依賴:不依賴于任何不可控組建
單元測試不依賴(lai)于任何不可控(kong)的(de)(de)組(zu)件,即(ji)使代碼中(zhong)依賴(lai)了其他(ta)這些不可控(kong)的(de)(de)組(zu)件(比如(ru)復雜外部系(xi)統,復雜模塊(kuai)或類),也需要通過(guo) mock 的(de)(de)方(fang)式將其變(bian)成可控(kong)。

3.測試意義:得到預期的輸入輸出
在你(ni)寫完一個(ge)功能代(dai)碼之(zhi)后,怎么保(bao)證你(ni)的代(dai)碼運(yun)行(xing)正確(que)?在各種異(yi)常(數(shu)據異(yi)常、輸入(ru)異(yi)常、調用(yong)異(yi)常等(deng))情況下,程序(xu)運(yun)行(xing)結果(guo)都符合你(ni)預(yu)先設(she)計的預(yu)期(qi),返(fan)回合適的報錯呢(ni)?這個(ge)時候(hou),單(dan)元測試(shi)就派(pai)上了用(yong)場

2 單元測試的工具

常(chang)用的單元測試工具(ju)有三種,在這里我們都會介紹,從(cong)基礎到進階的用法慢(man)慢(man)都會寫到

JUnit

經典中的經典,相信哪怕是入門java的學習者,絕大部分也使用過JUnit的@Test注解來進行單元測試,但目前已經更新到JUnit5,很多屬性是有變化的,SpringBoot默(mo)認(ren)集(ji)成的也是JUnit5版(ban)本,所以本文可能會(hui)與(yu)一些網上能找到的資(zi)料有(you)所不同

Asserts

就是我們常說的(de)&ldquo;斷言”,也是非(fei)常簡單好用(yong)的(de)工具,功能強悍,支持各種斷言方法

Mockito

Mock可能對于部分初學者來說比較陌生,所以這里多說一些:JUnit固然很簡單方便,但如果在單元測試的過程中嗎,需要構建請求頭和查詢參數、構建復雜的腳本等特殊需求,用標準的JUnit是無法實現的,這個時候我們就要用到MockMVC

什么是mock?

即mock object,模擬對象,是在OOP中模擬真實對象行為的假對象,在單元測試中有時無法使用真實對象,因此需要用到模擬對象來測試。

比如在以下情況可以采用模擬對象來替代真實對象:
1.真實對象的行為是不確定的 (例如,當前的時間或溫度) ;
2.真實對象很難搭建起來;
3.真實對象的行為很難觸發 (例如,網絡錯誤) ;
4.真實對象速度很慢(例如,一個完整的數據庫,在測試之前可能需要初始化) ;
5.真實的對象是用戶界面, 或包括用戶界面在內;
6.真實的對象使用了回調機制:
7.真實對象可能還不存在:
8.真(zhen)實對象可能(neng)包(bao)含不(bu)能(neng)用作(zuo)測(ce)試(shi)(而不(bu)是(shi)為實際工作(zuo))的信息和方(fang)法。

舉例:只做了一點簡單的更改,但是驗證需要重啟底層資源,一等就是五六分鐘;要模擬在某個操作系統/瀏覽器環境下的功能表現,總不能專門去搭一套環境吧

因此spring給我們提供了Mockito工具,即模擬對象的生成器,開發分為三個步驟:
1.模擬外部依賴,比如我們需要的底層數據、網絡請求頭等
2.執行具體的測試代碼
3.驗(yan)證(zheng)產(chan)生的(de)結果與(yu)預期(qi)是否相符

spring-test包提(ti)供(gong)了(le)一個核(he)心(xin)的(de)對(dui)象(xiang)——MockMVC,MockMvc是由spring-test包提(ti)供(gong),實現了(le)對(dui)Htp請(qing)求(qiu)的(de)模擬,能(neng)夠直接使(shi)用網(wang)絡(luo)的(de)形(xing)式,轉換到Controller的(de)調(diao)用,使(shi)得測試速度快(kuai).不依賴網(wang)絡(luo)環境。同時提(ti)供(gong)了(le)一套驗證的(de)工具, 結果的(de)驗證十分方便。

3 編寫單元測試的原則

一(yi)般來(lai)說(shuo),單元測試(shi)需要遵循3A模式(Arrange/Act/Assert)來(lai)編寫(xie)具體(ti)的代(dai)碼,具體(ti)的概念可以參考://juejin.cn/post/7005448543192252423

實際上3A是一個簡(jian)單實用的概念(nian),舉個簡(jian)單的例子:

Arrange:創建測試所需要的實例

Act:運行你所需要測試的具體行動(方法)

Assert:判斷返回的是否與預期一樣

4 Junit 5 實現基礎單元測試

1.建立相應的包存儲測試代碼
實(shi)際上(shang)Spring Boot對Junit進行(xing)了整合,我們可以(yi)從工(gong)程(cheng)(cheng)架構中看到,創建(jian)一(yi)(yi)個spring工(gong)程(cheng)(cheng)后(springinitializer),會自帶一(yi)(yi)個src下的(de)test包和(he)一(yi)(yi)個默認測(ce)試的(de)代碼(如下圖),如果沒有(you)的(de)話,也可以(yi)自己創建(jian)一(yi)(yi)個(因為有(you)些開發(fa)者可能創建(jian)的(de)是(shi)maven工(gong)程(cheng)(cheng))

2.引入依賴
當然要想使用這個測試,我們當然要引入相關的依賴,一般來說只要你創建了springboot項目,它是自動導入的。但是需要注意的是,我們通常需要排除junit-vintage-engine這個依賴,為什么呢?
如果不(bu)排除這個依賴,用的不(bu)會是(shi)spring整(zheng)合的Junit,從而(er)后面寫測試(shi)代(dai)碼(ma)的時(shi)候可能需要加(jia)RunWith注解來指(zhi)定是(shi)用哪一個Spring整(zheng)合的Junit

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <exclusions>
    <exclusion>
      <groupId>org.junit.vintage</groupId>
      <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
  </exclusions>
</dependency>

3.編寫測試代碼
這里我們(men)可以參(can)考(kao)給我們(men)的默認測(ce)試代碼(ma)來寫(xie),默認代碼(ma)會(hui)是(shi)這種(zhong)格(ge)式(@SpringBootTest就是(shi)spring整(zheng)合了junit之后的一個注解):

package wy.springboot01;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot01ApplicationTests {

    @Test
    void contextLoads() {
    }

}

我們(men)這里(li)簡單寫一(yi)下,新建一(yi)個User類,來讓測試代碼(ma)輸(shu)出User的(de)某個參數

package wy.springboot01.domain;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

/**
 * springboot項目啟動的時候,自動將application.yml內容加載到實體對象中
 */

@Data
//將實體類交給spring管理,自動掃描
@Component
public class User {
    private Integer uid;
    private String uname;
    private String password;
    private ArrayList<String> addrs;

    public User() {
    }

    public User(Integer uid, String uname, String password, ArrayList<String> addrs) {
        this.uid = uid;
        this.uname = uname;
        this.password = password;
        this.addrs = addrs;
    }
}

單元測試代碼:

package wy.springboot01;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import wy.springboot01.domain.User;

@SpringBootTest(classes = {User.class})
class Springboot01ApplicationTests {

    @Autowired
    private User user;
    @Test
    void contextLoads() {
        System.out.println(user.getAddrs());
    }

}

4.直接運行測試代碼,可能會失敗,常見錯誤是:Found multiple @SpringBootConfiguration annotated classes,這是因為之前在做其他代碼的時候,可能有多個文件用了@SpringBootConfiguration從而導致沖突,只需要注釋掉多余的只留一個即可,或者指定你要用哪個@SpringBootConfiguration來測試,因此我們這里用了:@SpringBootTest(classes = {User.class})

5.正常會輸出null,因(yin)為我們(men)并沒有給Addrs這(zhe)個參(can)數賦默認值(zhi),到這(zhe)里我們(men)就完成了(le)一(yi)(yi)個沒有3A的(de)最(zui)后一(yi)(yi)個A(斷(duan)言)的(de)單元測試(shi)

6.但是我們在開發中通常不會只測試某一個類的參數,比如我們想測試在某個啟動類中(某個功能加載的過程中)User對象的值是否載入成功,那么也可以按如下來實現:
首(shou)先(xian)我們(men)先(xian)寫一個yml文件(jian)來給User賦默認值,就比如叫做application.yml

user:
  uid: 1998051110
  uname: wuyu
  password: wuyu1999
  addrs:
    - Beijing
    - Sichuan
    - Nanchang

然后給User類加上注解:

//加載配置內容,設定配置前綴,注意:prefix參數不支持小駝峰原則,必須全部小寫
@ConfigurationProperties(prefix = "user")

最后把測試的(de)(de)啟動(dong)(dong)類(lei)改(gai)成這個工程的(de)(de)默(mo)認啟動(dong)(dong)類(lei):

@SpringBootTest(classes = {Springboot01Application.class})

這樣啟(qi)動之后我們(men)就會得到如下結果,默認值加載成功了:

[Beijing, Sichuan, Nanchang]

7.那么如何給結果做一個斷言呢?換而言之我不需要用人眼去判斷結果是不是對的,系統就西東判斷了,也很簡單,這里我們就要介紹到我們的第二種測試工具:asserts

5 Asserts方法判斷返回值

實(shi)際上我們(men)在日常寫代碼中有時候也會用(yong)到assert,比如判斷(duan)某個入參(can)是否(fou)為(wei)空,直接使用(yong):assert data.length != 0; 即可

那(nei)么回(hui)(hui)到單元測試,我們如(ru)何(he)判斷(duan)返回(hui)(hui)值(zhi)?

//判斷方法返回是否為Flase
Assertions.assertFalse(SomeClass.someMethod());
//判斷方法返回是否為True
Assertions.assertTure(SomeClass.someMethod());
//判斷方法返回是否與對應值相等
Assertions.assertTure("預期返回", SomeClass.someMethod());

同樣將剛剛的Juint判(pan)斷user的例子擴充一下(xia),我(wo)們判(pan)斷用戶名是(shi)否與預期一致(zhi),可以(yi)寫成:

@SpringBootTest(classes = {Springboot01Application.class})
class Springboot01ApplicationTests {

    @Autowired
    private User user;
    @Test
    void contextLoads() {
        Assertions.assertArrayEquals(String.join("","Beijing", "Sichuan", "Nanchang" ).toCharArray()
                ,String.join("",user.getAddrs() ).toCharArray());
    }

}

最終運行(xing)結果(guo)如下(xia)圖,就(jiu)實現(xian)了(le)不由人眼判(pan)斷而是系(xi)統判(pan)斷,非常方(fang)便:

6 MockMVC

剛剛說過,mock就是用來模擬一些不太好去創建的代碼外部依賴,最典型的就是對于controller的測試,傳統的方法是代碼起來之后使用postman,而單元測試要求我們不能有這種外部依賴,因此我們就用mock來模擬這樣的一個環境

還(huan)是拿user類(lei)來舉例,比如(ru)我們(men)有一個controller長這(zhe)個樣子,如(ru)果我們(men)要驗證他的(de)返(fan)回是不是對(dui)的(de),按(an)照常理我們(men)應該去(qu)啟動spring并且用瀏覽器、postman、http腳本等來看看它(ta)的(de)返(fan)回是什么,非常的(de)浪費時間(jian)

@GetMapping("/user")
public ArrayList<User> getUser(){
    System.out.println("user get......");
    ArrayList<User> users = new ArrayList<>();
    users.add(new User(1001,"wu","1212",new ArrayList<>(Arrays.asList("nanchang", "sichuan", "beijing"))));
    users.add(new User(1002,"du","1313",new ArrayList<>(Arrays.asList("chang", "sica", "beng"))));
    return users;
}

那么用mock怎么去做呢?
首先(xian)我(wo)們先(xian)在test包下,創建一個與(yu)默認(ren)測試代(dai)碼平(ping)行的測試類,比如就叫(jiao)MockMVCTester,如下:

package wy.springboot01;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import javax.annotation.Resource;
//進行每一次mock模擬tomcat容器的時候,使用隨機端口啟動,這樣不會有端口占用的問題
@SpringBootTest(classes = {Springboot01Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//自動配置以及啟用mvc對象
@AutoConfigureMockMvc
public class MockMVCTester {
    //注入MockMVC對象,它是springtest依賴中自帶的
    @Resource
    private MockMvc mockMvc;
    @Test
    public void testMock() throws Exception {
        //獲取mock返回的對象
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/user"))//perform模擬一個http請求,這里是get方法
                .andExpect(MockMvcResultMatchers.status().isOk())//添加預期,如果服務器返回的是200
                .andDo(MockMvcResultHandlers.print())//那我們就把請求和響應的信息在控制臺中打印輸出
                .andReturn();//將結果返回出來
    }
}

啟動測試方法testMock,可以發(fa)現:

同(tong)時也(ye)會返回controller的信息,可(ke)以對照是否(fou)符合預期,這樣我們就實(shi)現(xian)了不(bu)需要啟動瀏(liu)覽(lan)器、postman等即可(ke)測試controller

但我們還可以更加便利一些,直接在運行的時候就判斷它的返回是不是符合預期,本例的返回為:
[{"uid":1001,"uname":"wu","password":"1212","addrs":["nanchang","sichuan","beijing"]},{"uid":1002,"uname":"du","password":"1313","addrs":["chang","sica","beng"]}]
因此我們將(jiang)測試代碼做一些改動

package wy.springboot01;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import javax.annotation.Resource;
//進行每一次mock模擬tomcat容器的時候,使用隨機端口啟動,這樣不會有端口占用的問題
@SpringBootTest(classes = {Springboot01Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//自動配置以及啟用mvc對象
@AutoConfigureMockMvc
public class MockMVCTester {
    //注入MockMVC對象,它是springtest依賴中自帶的
    @Resource
    private MockMvc mockMvc;
    @Test
    public void testMock() throws Exception {
        //獲取mock返回的對象
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/user"))//perform模擬一個http請求,這里是get方法
                .andExpect(MockMvcResultMatchers.status().isOk())//添加預期,如果服務器返回的是200
                .andDo(MockMvcResultHandlers.print())//那我們就把請求和響應的信息在控制臺中打印輸出
                .andExpect(MockMvcResultMatchers.content().string("[{\"uid\":1001,\"uname\":\"wu\"," +
                        "\"password\":\"1212\",\"addrs\":[\"nanchang\",\"sichuan\",\"beijing\"]}," +
                        "{\"uid\":1002,\"uname\":\"du\",\"password\":\"1313\",\"addrs\"" +
                        ":[\"chang\",\"sica\",\"beng\"]}]"))//content表示對于返回的請求體數據進行判斷,string表示進行比對
                .andReturn();//將結果返回出來
    }
}

再次啟動,運行依然成(cheng)功說(shuo)明比(bi)對正確(que),返回符(fu)合預期,如(ru)果這個時候(hou)我(wo)們改了controller的(de)邏(luo)輯,則返回“test fail”,非常好用,甚至會比(bi)對預期值和實際值

注:如果覺得testMock()看起來不舒服,可以在@Test下面加注解@DisplayName("get方法測試用例"),來自定義測試方法名稱
如(ru)果有(you)入參(can)、且返回是一個json可(ke)以參(can)考如(ru)下案(an)例:

@Test
@DisplayName("get方法+有入參+有json返回")
public void testMock1() throws Exception {
    //mock返回的對象可以不獲取,因為單純的判斷對錯用不上
    mockMvc.perform(MockMvcRequestBuilders.get("/user/para")//perform模擬一個http請求,這里是get方法
                    .header("token", "akakak")//請求頭
                    .param("id","wy")//請求參數
                    .param("password","asd"))//請求參數
            .andExpect(MockMvcResultMatchers.status().isOk())//添加預期,如果服務器回的是200
            .andDo(MockMvcResultHandlers.print())//那我們就把請求和響應的信息在控制臺中打印輸出
            .andExpect(MockMvcResultMatchers.jsonPath("ak").value("asd"))//獲取返回的json并核對對應的值是否一樣
            .andReturn();//將結果返回出來
}

如果方法為post,可以參考如下案例

@Test
@DisplayName("post方法測試用例")
public void testMock1() throws Exception {
    //IoC
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("IoC.xml");
    User user = context.getBean(User.class);
    ObjectMapper mapper = new ObjectMapper();
    user.setUname("wy");
    //mock返回的對象可以不獲取,因為單純的判斷對錯用不上
    mockMvc.perform(MockMvcRequestBuilders.post("/user")//perform模擬一個http請求,這里是get方法
                    .content(mapper.writeValueAsString(user))//用IoC建立一個User對象
                    .contentType(MediaType.APPLICATION_JSON_VALUE))//添加json類數據,轉化為入參
            .andExpect(MockMvcResultMatchers.status().isOk())//添加預期,如果服務器回的是200
            .andDo(MockMvcResultHandlers.print())//那我們就把請求和響應的信息在控制臺中打印輸出
            .andExpect(MockMvcResultMatchers.jsonPath("uname").value("wy"))//獲取返回的json并核對對應的值是否一樣
            .andReturn();//將結果返回出來
}
文章來自個人專欄
文(wen)章 | 訂閱
0條評論
0 / 1000
請輸入你的評論
0
0