1. 程式人生 > >JUnit5學習之一:基本操作

JUnit5學習之一:基本操作

### 歡迎訪問我的GitHub [https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) 內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等; ### 關於《JUnit5學習》系列 《JUnit5學習》系列旨在通過實戰提升SpringBoot環境下的單元測試技能,一共八篇文章,連結如下: 1. [基本操作](https://blog.csdn.net/boling_cavalry/article/details/108810587) 2. [Assumptions類](https://blog.csdn.net/boling_cavalry/article/details/108861185) 3. [Assertions類](https://blog.csdn.net/boling_cavalry/article/details/108899437) 4. [按條件執行](https://blog.csdn.net/boling_cavalry/article/details/108909107) 5. [標籤(Tag)和自定義註解](https://blog.csdn.net/boling_cavalry/article/details/108914091) 6. [引數化測試(Parameterized Tests)基礎](https://blog.csdn.net/boling_cavalry/article/details/108930987) 7. [引數化測試(Parameterized Tests)進階](https://blog.csdn.net/boling_cavalry/article/details/108942301) 8. [綜合進階(終篇)](https://blog.csdn.net/boling_cavalry/article/details/108952500) ### 本篇概覽 本文是《JUnit5學習》系列的第一篇,通過實戰學習在SpringBoot框架下JUnit5的基本功能,全篇章節如下: 1. JUnit5簡介 2. SpringBoot對JUnit5的依賴 3. 常用註解簡介 4. 5版本已廢棄的註解介紹 5. 進入實戰環節,先介紹版本和環境資訊 6. 建立《JUnit5學習》系列原始碼的父工程 7. 建立子工程,編碼體驗常用註解 ### 關於JUnit5 1. JUnit是常用的java單元測試框架,5是當前最新版本,其整體架構如下(圖片來自網路): ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081508389-652712027.png) 2. 從上圖可見,整個JUnit5可以劃分成三層:頂層框架(Framework)、中間的引擎(Engine),底層的平臺(Platform); 3. 官方定義JUnit5由三部分組成:Platform、Jupiter、Vintage,功能如下; 4. Platform:位於架構的最底層,是JVM上執行單元測試的基礎平臺,還對接了各種IDE(例如IDEA、eclipse),並且還與引擎層對接,定義了引擎層對接的API; 5. Jupiter:位於引擎層,支援5版本的程式設計模型、擴充套件模型; 6. Vintage:位於引擎層,用於執行低版本的測試用例; - 可見整個Junit Platform是開放的,通過引擎API各種測試框架都可以接入; ### SpringBoot對JUnit5的依賴 1. 這裡使用SpringBoot版本為2.3.4.RELEASE,在專案的pom.xml中依賴JUnit5的方法如下: ```xml ``` 2. 如下圖紅框,可見JUnit5的jar都被spring-boot-starter-test間接依賴進來了: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081512317-591383986.jpg) ### 曾經的RunWith註解 1. 在使用JUnit4的時候,咱們經常這麼寫單元測試類: ```java @RunWith(SpringRunner.class) @SpringBootTest public class XXXTest { ``` 2. 對於上面的RunWith註解,JUnit5官方文件的說法如下圖紅框所示,已經被ExtendWith取代: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081512817-1824269816.jpg) 3. 咱們再來看看SpringBootTest註解,如下圖,可見已經包含了ExtendWith: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081513101-1273618409.jpg) 4. 綜上所述,SpringBoot+JUnit5時,RunWith註解已經不需要了,正常情況下僅SpringBootTest註解即可,如果對擴充套件性有更多需求,可以新增ExtendWith註解,如下圖: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081513340-1191875119.jpg) ### 常用的JUnit5註解(SpringBoot環境) 注意,接下來提到的測試方法,是指當前class中所有被@Test、@RepeatedTest、@ParameterizedTest、@TestFactory修飾的方法; 1. ExtendWith:這是用來取代舊版本中的RunWith註解,不過在SpringBoot環境如果沒有特別要求無需額外配置,因為SpringBootTest中已經有了; 2. Test:被該註解修飾的就是測試方法; 3. BeforeAll:被該註解修飾的必須是靜態方法,會在所有測試方法之前執行,會被子類繼承,取代低版本的BeforeClass; 4. AfterAll:被該註解修飾的必須是靜態方法,會在所有測試方法執行之後才被執行,會被子類繼承,取代低版本的AfterClass; 5. BeforeEach:被該註解修飾的方法會在每個測試方法執行前被執行一次,會被子類繼承,取代低版本的Before; 6. AfterEach:被該註解修飾的方法會在每個測試方法執行後被執行一次,會被子類繼承,取代低版本的Before; 7. DisplayName:測試方法的展現名稱,在測試框架中展示,支援emoji; 8. Timeout:超時時長,被修飾的方法如果超時則會導致測試不通過; 9. Disabled:不執行的測試方法; ### 5版本已廢棄的註解 以下的註解都是在5之前的版本使用的,現在已經被廢棄: | 被廢棄的註解 | 新的繼任者 | |--|--| | Before | BeforeEach | | After | AfterEach | | BeforeClass | BeforeAll | | AfterClass | AfterAll | | Category | Tag | | RunWith | ExtendWith | | Rule | ExtendWith | | ClassRule | RegisterExtension | ### 版本和環境資訊 整個系列的編碼和執行在以下環境進行,供您參考: 1. 硬體配置:處理器i5-8400,記憶體32G,硬碟128G SSD + 500G HDD 2. 作業系統:Windows10家庭中文版 3. IDEA:2020.2.2 (Ultimate Edition) 4. JDK:1.8.0_181 5. SpringBoot:2.3.4.RELEASE 6. JUnit Jupiter:5.6.2 接下來開始實戰,咱們先建好SpringBoot專案; ### 關於lombok 為了簡化程式碼,專案中使用了lombok,請您在IDEA中安裝lombok外掛; ### 原始碼下載 1. 如果您不想編碼,可以在GitHub下載所有原始碼,地址和連結資訊如下表所示(https://github.com/zq2599/blog_demos): | 名稱 | 連結 | 備註| | :-------- | :----| :----| | 專案主頁| https://github.com/zq2599/blog_demos | 該專案在GitHub上的主頁 | | git倉庫地址(https)| https://github.com/zq2599/blog_demos.git | 該專案原始碼的倉庫地址,https協議 | | git倉庫地址(ssh)| [email protected]:zq2599/blog_demos.git | 該專案原始碼的倉庫地址,ssh協議 | 2. 這個git專案中有多個資料夾,本章的應用在junitpractice資料夾下,如下圖紅框所示: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081513555-2046624807.jpg) 3. junitpractice是父子結構的工程,本篇的程式碼在junit5experience子工程中,如下圖: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081513997-472684688.jpg) ### 建立Maven父工程 1. 為了便於管理整個系列的原始碼,在此建立名為junitpractice的maven工程,後續所有實戰的原始碼都作為junitpractice的子工程; 2. junitpractice的pom.xml如下,可見是以SpringBoot的2.3.4.RELEASE版本作為其父工程: ```xml ``` ### 本篇的原始碼工程 接下來咱們準備一個簡單的SpringBoot工程用於做單元測試,該工程有service和controller層,包含一些簡單的介面和類; 1. 建立名為junit5experience的子工程,pom.xml如下,注意單元測試要依賴spring-boot-starter-test: ```xml ``` 2. 寫一些最簡單的業務程式碼,首先是service層的介面HelloService.java: ```java package com.bolingcavalry.junit5experience.service; public interface HelloService { String hello(String name); int increase(int value); /** * 該方法會等待1秒後返回true,這是在模擬一個耗時的遠端呼叫 * @return */ boolean remoteRequest(); } ``` 3. 上述介面對應的實現類如下,hello和increase方法分別返回String型和int型,remoteRequest故意sleep了1秒鐘,用來測試Timeout註解的效果: ```java package com.bolingcavalry.junit5experience.service.impl; import com.bolingcavalry.junit5experience.service.HelloService; import org.springframework.stereotype.Service; @Service() public class HelloServiceImpl implements HelloService { @Override public String hello(String name) { return "Hello " + name; } @Override public int increase(int value) { return value + 1; } @Override public boolean remoteRequest() { try { Thread.sleep(1000); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); } return true; } } ``` 4. 新增一個簡單的controller: ```java package com.bolingcavalry.junit5experience.controller; import com.bolingcavalry.junit5experience.service.HelloService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired private HelloService helloService; @RequestMapping(value = "/{name}", method = RequestMethod.GET) public String hello(@PathVariable String name){ return helloService.hello(name); } } ``` 5. 啟動類: ```java package com.bolingcavalry.junit5experience; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Junit5ExperienceApplication { public static void main(String[] args) { SpringApplication.run(Junit5ExperienceApplication.class, args); } } ``` - 以上就是一個典型的web工程,接下來一起為該工程編寫單元測試用例; ### 編寫測試程式碼 1. 在下圖紅框位置新增單元測試類: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081514547-1518877041.jpg) 2. 測試類的內容如下,涵蓋了剛才提到的常用註解,請注意每個方法的註釋說明: ```java package com.bolingcavalry.junit5experience.service.impl; import com.bolingcavalry.junit5experience.service.HelloService; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest @Slf4j class HelloServiceImplTest { private static final String NAME = "Tom"; @Autowired HelloService helloService; /** * 在所有測試方法執行前被執行 */ @BeforeAll static void beforeAll() { log.info("execute beforeAll"); } /** * 在所有測試方法執行後被執行 */ @AfterAll static void afterAll() { log.info("execute afterAll"); } /** * 每個測試方法執行前都會執行一次 */ @BeforeEach void beforeEach() { log.info("execute beforeEach"); } /** * 每個測試方法執行後都會執行一次 */ @AfterEach void afterEach() { log.info("execute afterEach"); } @Test @DisplayName("測試service層的hello方法") void hello() { log.info("execute hello"); assertThat(helloService.hello(NAME)).isEqualTo("Hello " + NAME); } /** * DisplayName中帶有emoji,在測試框架中能夠展示 */ @Test @DisplayName("測試service層的increase方法\uD83D\uDE31") void increase() { log.info("execute increase"); assertThat(helloService.increase(1)).isEqualByComparingTo(2); } /** * 不會被執行的測試方法 */ @Test @Disabled void neverExecute() { log.info("execute neverExecute"); } /** * 呼叫一個耗時1秒的方法,用Timeout設定超時時間是500毫秒, * 因此該用例會測試失敗 */ @Test @Timeout(unit = TimeUnit.MILLISECONDS, value = 500) @Disabled void remoteRequest() { assertThat(helloService.remoteRequest()).isEqualTo(true); } } ``` 3. 接下來執行測試用例試試,點選下圖紅框中的按鈕: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081514804-1138080473.jpg) 4. 如下圖,在彈出的選單中,點選紅框位置: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081515077-1037727870.jpg) 5. 執行結果如下,可見Displayname註解的值作為測試結果的方法名展示,超時的方法會被判定為測試不通過,Disable註解修飾的方法則被標記為跳過不執行: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081515633-490622668.jpg) 6. 在父工程junitpractice的pom.xml檔案所在目錄,執行mvn test命令,可以看到maven執行單元測試的效果: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202102/485422-20210219081516585-1528356270.jpg) - 至此,咱們對SpringBoot環境下的JUnit5有了最基本的瞭解,接下來的章節會展開更多知識點和細節,對單元測試做更深入的學習。 ### 你不孤單,欣宸原創一路相伴 1. [Java系列](https://xinchen.blog.csdn.net/article/details/105068742) 2. [Spring系列](https://xinchen.blog.csdn.net/article/details/105086498) 3. [Docker系列](https://xinchen.blog.csdn.net/article/details/105086732) 4. [kubernetes系列](https://xinchen.blog.csdn.net/article/details/105086794) 5. [資料庫+中介軟體系列](https://xinchen.blog.csdn.net/article/details/105086850) 6. [DevOps系列](https://xinchen.blog.csdn.net/article/details/105086920) ### 歡迎關注公眾號:程式設計師欣宸 > 微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界... [https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos)