系列文章目錄
這是CSDN postnull 博客《學透Spring Boot》系列的一篇,更多文章請移步:Postnull - 學透Spring Boot系列文章
文章目錄
- 系列文章目錄
- 前言
- 1. 基本概念
- UT 單元測試
- TDD 測試驅動開發
- UT測試框架
- Mock框架
- 3. Spring Test
- 為什么要用Spring Test
- 引入Spring Test
- Spring Test最簡單使用
前言
開發的過程中,寫業務代碼是工作的一部分,測試也是工作的一部分。尤其是作為開發人員,用單元測試來測試我們的代碼,可以更早的發現bug。
1. 基本概念
UT 單元測試
單元測試 Unit Testing,通常是開發的一部分,也是開發過程中最小的測試單元。主要測試一個類或一個方法的邏輯。
因為只是保證我們自己寫的代碼的邏輯,所以是不涉及外部服務的(比如數據庫和外部接口),外部服務我們通常使用mock來模擬它們的行為。
UT是代碼片段的測試
外部集成測試才需要依賴真正的外部服務
TDD 測試驅動開發
近些年流行很多種 XDD,其中和測試相關的就是TDD,Test driven development,這是一種開發方法。
以前傳統的開發模式,我們先一口氣寫完業務代碼,然后再開始寫單元測試。但是會遇到一些問題:
- 業務代碼不方便測試
- 測試時才發現bug,導致業務代碼要做很大的調整
所以我們可以換一種思路,先寫測試用例,再寫業務代碼。
這也就是TDD的三個步驟,遵循 紅-綠-重構(Red-Green-Refactor)
- 紅:編寫一個失敗的測試。
- 綠:編寫代碼使測試通過。
- 重構:優化代碼,確保測試仍然通過。
我們先快速試一下
先寫個測試用例
public class CalculatorTest {@Testpublic void testAdd1() {Calculator calculator = new Calculator();assertEquals(3, calculator.add(1, 2));}@Testpublic void testAdd2() {Calculator calculator = new Calculator();assertEquals(0, calculator.add(1, -1));}
}
這個時候,測試用例是通不過的,因為我們的業務代碼類都還沒實現,編譯錯誤。當然這樣體驗不太好,我們也可以先實現業務代碼的骨架,只是空實現。
public class Calculator {public Integer add(int a, int b) {return null;}
}
我們運行一下測試用例,UT不通過,這也是預期的。
然后我們開始實現代碼
public class Calculator {public Integer add(int a, int b) {return 0;}
}
這時候單元用例部分通過了
說明我們的代碼有問題,繼續調整
public class Calculator {public Integer add(int a, int b) {return a + b;}
}
這次我們的UT都過了,表示代碼基本沒問題了。(基本沒問題不表示絕對沒問題,因為可能測試覆蓋率不夠,有些邊緣的case沒有考慮到。)
下次,我們的業務代碼被修改了,理論上出了bug,我們的測試用例是要失敗的。
public class Calculator {public Integer add(int a, int b) {if(a < 0 || b < 0){return null;}return a + b;}
}
這時候,我們就需要注意了,是代碼有問題,還是測試用例需要更新。
轉換思路——測試先行
UT測試框架
Java最常用的 UT 框架有兩個
- Junit
- TestNG
其中Junit比較輕量,滿足大部分場景。TestNG功能更強大,比如支持并行測試(同時運行多個測試用例,Junit只能一個完了后再跑下一個)。具體對比:
- TestNG 的@DataProvider,注入測試數據更方便,Junit5后提供的@ParameteriedTest也差不多功能
- TestNG支持測試依賴,運行A用例前會自動先觸發B用例
- TestNG支持測試分組,方便運行一組測試用例
- TestNG支持用例并行執行,對相對獨立的case可以大大加快UT的時間
- ……
TestNG更強大,但有時簡單就好
Mock框架
前面也說過,對于外部依賴,甚至是業務代碼其它的類,我們都應該模擬,這樣保證我們可以關注被測試的這個類和這個方法。
保證測試用例的能夠獨立、快速的被測試
有很多Mock框架
- Mockito:最常用
- PowerMock:兼容Mockito,功能更強大,比如測試靜態方法和私有方法(反思:private方法真的應該被測試嗎)
比如使用Mockito,步驟基本都是差不多的,先創建mock對象和行為,然后調用方法,最后驗證結果
public class UserServiceTest {@Testpublic void testGetUser() {// 1. 創建 Mock 對象UserRepository mockRepo = mock(UserRepository.class);// 2. 設置 Mock 行為when(mockRepo.findById(1)).thenReturn(new User(1, "John"));// 3. 調用測試方法UserService userService = new UserService(mockRepo);User user = userService.getUser(1);// 4. 驗證結果assertEquals("John", user.getName());}
}
3. Spring Test
為什么要用Spring Test
對于Java項目,直接使用TestNG/Junit + Mockito 不就可以了嗎?為什么還要用 Spring Test?
以前也有這個疑問,但是如果我們測試Spring 項目時,Junit + Mockito 就有點力不從心了。比如:
- 注入Spring的配置,比如application-test.xml中配置,注入到Spring容器中去
- 自動注入Bean,這個做不到,得手動new對象
- 模擬HTTP請求,這個也做不到,所以Controller測試不了
- 測試Spring整個應用,也做不到,只能測試單個類
Spring Test就是為了更方便的測試Spring 應用
當然,Spring Test不是要取代 TestNG/Junit + Mockito, 想法,它底層用的還是這些技術。
它只是提供了一堆工具和注解,幫助我們更方便測試Spring應用。
引入Spring Test
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
我們在關聯依賴中也可以看到,它自動引入了Junit和Mockito
這框架本身包含了兩個模塊
- Spring Test 核心模塊
spring-boot-test
- 自動配置模塊
spring-boot-test-autoconfigure
: 我們很多自動配置,比如注入
比如在自動配置的模塊中,我們可以看到MockMvc
的自動配置。這個類我們以后測試會經常遇到。
Spring Test最簡單使用
本文我們先來個最簡單的例子,測試我們的Controller。
@RestController
@RequestMapping("/tn-users")
public class TnUserController {private TnUserService tnUserService;public TnUserController(TnUserService tnUserService) {this.tnUserService = tnUserService;}@GetMapping("/{id}")public ResponseEntity<TypiUser> getUser(@PathVariable int id) {TypiUser user = tnUserService.getUserById(id);return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();}
}
然后我們編寫測試用例
@WebMvcTest(TnUserController.class)
public class TnUserControllerTest {@Autowiredprivate MockMvc mockMvc;@MockitoBeanprivate TnUserService tnUserService;@Test@DisplayName("測試成功查詢用戶的情況")public void testGetUser() throws Exception {//givenTypiUser mockUser = TypiUser.builder().id(1234).name("Joe").build();//whenwhen(tnUserService.getUserById(eq(1234))).thenReturn(mockUser);//thenmockMvc.perform(get("/tn-users/{id}", 1234)).andExpect(status().isOk()).andExpect(jsonPath("$.id").value(1234)).andExpect(jsonPath("$.name").value("Joe"));}
}
測試通過
稍微改一下代碼,再次運行,會發現報錯。
mockMvc.perform(get("/tn-users/{id}", 1234)).andExpect(status().isBadRequest());
可以看到,我們現在具備測試Controller層的能力了。如果沒有Spring Test,只是靠Mockito基本做不到接口層的測試。
我們先不用關注實現的細節。下一篇文章我們會全面介紹Spring Test的使用。