點贊再看,動力無限。 微信搜「 程序猿阿朗 」。
本文 Github.com/niumoo/JavaNotes 和 未讀代碼博客 已經收錄,有很多知識點和系列文章。
在軟件開發過程中,我們通常都需要測試自己的代碼運行是否正常,可能對一個函數進行簡單測試,也可能是多個功能的組合測試。不管使用哪種方式,都是爲了更好的測試我們的代碼是否存在邏輯缺陷。測試對於軟件開發是非常必要的。
JUnit 5 介紹
在 Java 中比較有名的測試工具是 JUnit ,通常我們使用 JUnit 可以對一個邏輯單元進行測試,因此也叫單元測試。多個單元測試組合測試,可以確保我們的程序符合預期。JUnit 單元測試可以在開發階段發現問題,讓我們可以提前修復代碼,因此十分重要。
JUnit 5 和 JUnit
JUnit 是一個 Java 語言的開源測試框架,使用 JUnit 讓我們使用註解就可以進行單元測試,很是方便。
JUnit 5 是 JUnit 的升級版本,JUnit 5 使用了 Java 8 及更高版本的 Java 語言特性,如函數編程,流式編碼等,因此更加強大。JUnit 5 進行單元測試的可讀性更強,編寫更加容易,且可以輕鬆擴展。
JUnit 5 基本組件
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform
JUnit Platform 是 JUnit 的基礎框架,使用 JUnit Platform 才能在 JVM 啓動測試,JUnit Platform 還定義了 TestEngine
測試引擎,是JUnit 測試的基礎。
JUnit Jupiter
JUnit Jupiter 提供了單元測試常見的註解以及擴展接口,想要方便的進行 JUnit 單元測試,那麼 Jupiter 模塊就必不可少。
JUnit Vintage
JUnit Vintage 提供了對 JUnit 3 和 JUnit 4 的測試支持。
JUnit 5 依賴
使用註解進行 JUnit 單元測試,直接引入 junit-jupiter
即可。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
JUnit 5 常用註解
@Test
爲一個 public void
方法添加 @Test
註釋,允許我們對這個方法進行測試。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author:https://www.wdbyte.com
**/
class JUnitTestIsDog {
@Test
public void testIsDog() {
String name = "cat";
Assertions.assertEquals(name, "dog");
}
}
上面的代碼中使用了 Assertions.assertEquals(name, "dog")
來判斷是否 name
變量是否是 dog
,Assertions
是 JUnit
提供的斷言工具,後面會詳細介紹。
在 idea
中運行可以到的錯誤日誌,提示預期是 dog
,實際是 cat
org.opentest4j.AssertionFailedError:
Expected :cat
Actual :dog
<Click to see difference>
如果是符合預期的,那麼運行會顯示正確標誌。
@Test
public void testIsDog2() {
String name = "dog";
Assertions.assertEquals(name, "dog");
}
testIsDog2
方法測試通過。
@BeforeAll
使用 @BeforeAll
可以在單元測試前初始化部分信息,@BeforeAll
只能使用在靜態方法上,被註解的方法會在測試開始前運行一次。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* @author:https://www.wdbyte.com
**/
class JUnitBeforeAll {
@BeforeAll
public static void init() {
System.out.println("初始化,準備測試信息");
}
@Test
public void testIsDog() {
String name = "dog";
Assertions.assertEquals(name, "dog");
System.out.println("is dog");
}
@Test
public void testIsCat() {
String name = "cat";
Assertions.assertEquals(name, "cat");
System.out.println("is cat");
}
}
這會輸出:
初始化,準備測試信息
is cat
is dog
@BeforeEach
使用 @BeforeEach
註解的方法,會在每一個 @Test
註解的方法運行前運行一次。
class JUnitBeforeAll {
@BeforeAll
public static void init() {
System.out.println("初始化,準備測試信息");
}
@BeforeEach
public void start(){
System.out.println("開始測試...");
}
@Test
public void testIsDog() {
String name = "dog";
Assertions.assertEquals(name, "dog");
System.out.println("is dog");
}
@Test
public void testIsCat() {
String name = "cat";
Assertions.assertEquals(name, "cat");
System.out.println("is cat");
}
}
這會輸出:
初始化,準備測試信息
開始測試...
is cat
開始測試...
is dog
@AfterAll
@AfterAll
註解只能使用在靜態方法上,被註解的方法會在所有單元測試運行完畢後運行一次。
class JUnitBeforeAll {
@BeforeAll
public static void init() {
System.out.println("初始化,準備測試信息");
}
@BeforeEach
public void start(){
System.out.println("開始測試...");
}
@Test
public void testIsDog() {
//...
}
@Test
public void testIsCat() {
//...
}
@AfterAll
public static void close() {
System.out.println("結束,準備退出測試");
}
}
這會輸出:
初始化,準備測試信息
開始測試...
is cat
開始測試...
is dog
結束,準備退出測試
@AfterEach
使用 @AfterEach
註解的方法,會在每一個 @Test
註解的方法運行結束前運行一次。
class JUnitBeforeAll {
@BeforeAll
public static void init() {
System.out.println("初始化,準備測試信息");
}
@BeforeEach
public void start(){
System.out.println("開始測試...");
}
@Test
public void testIsDog() { //... }
@Test
public void testIsCat() { //... }
@AfterEach
public void end(){
System.out.println("測試完畢...");
}
@AfterAll
public static void close() {
System.out.println("結束,準備退出測試");
}
}
這會輸出:
初始化,準備測試信息
開始測試...
is cat
測試完畢...
開始測試...
is dog
測試完畢...
結束,準備退出測試
@Disabled
被 @Disabled
註解的方法不在參與測試,下面對 testIsDog
方法添加了 @Disabled
註解。
class JUnitBeforeAll {
@BeforeAll
public static void init() {
System.out.println("初始化,準備測試信息");
}
@BeforeEach
public void start(){
System.out.println("開始測試...");
}
@Disabled("由於xx原因,關閉 testIsDog 測試")
@Test
public void testIsDog() {
String name = "dog";
Assertions.assertEquals(name, "dog");
System.out.println("is dog");
}
@Test
public void testIsCat() {
String name = "cat";
Assertions.assertEquals(name, "cat");
System.out.println("is cat");
}
@AfterEach
public void end(){
System.out.println("測試完畢...");
}
@AfterAll
public static void close() {
System.out.println("結束,準備退出測試");
}
}
這會輸出:
初始化,準備測試信息
開始測試...
is cat
測試完畢...
由於xx原因,關閉 testIsDog 測試
結束,準備退出測試
@DisplayName
使用 @DisplayName
註解可以自定義測試方法的顯示名稱,下面爲兩個測試方法自定義名稱。
class JUnitBeforeAll {
@BeforeAll
public static void init() {
System.out.println("初始化,準備測試信息");
}
@BeforeEach
public void start() {
System.out.println("開始測試...");
}
@DisplayName("是否是狗")
@Disabled
@Test
public void testIsDog() {
String name = "dog";
Assertions.assertEquals(name, "dog");
System.out.println("is dog");
}
@DisplayName("是否是貓")
@Test
public void testIsCat() {
String name = "cat";
Assertions.assertEquals(name, "cat");
System.out.println("is cat");
}
@AfterEach
public void end() {
System.out.println("測試完畢...");
}
@AfterAll
public static void close() {
System.out.println("結束,準備退出測試");
}
}
在 idea
中運行後,可以看到配置的中文名稱。
@ParameterizedTest
使用註解 @ParameterizedTest
結合 @ValueSource
,可以對不用的入參進行測試。下面的示例使用 @ParameterizedTest
來開始參數化單元測試,name
屬性用來定義測試名稱, @ValueSource
則定義了兩個測試值。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class JUnitParam {
//@Test
@DisplayName("是否是狗")
@ValueSource(strings = {"dog", "cat"})
@ParameterizedTest(name = "開始測試入參 {0} ")
public void testIsDog(String name) {
Assertions.assertEquals(name, "dog");
}
}
這會輸出:
@Order
在類上增加註解 @TestMethodOrder
,然後在方法上使用 @Order
指定順序,數字越小優先級越搞,可以保證測試方法運行順序。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.condition.EnabledOnJre;
import static org.junit.jupiter.api.condition.JRE.JAVA_19;
@TestMethodOrder(OrderAnnotation.class)
public class JUnitOrder{
@Test
@DisplayName("測試是否是狗")
@Order(2)
public void testIsDog() {
String name = "dog";
Assertions.assertEquals(name, "dog");
System.out.println("is dog");
}
@DisplayName("是否是貓")
@Test
@Order(1)
public void testIsCat() {
String name = "cat";
Assertions.assertEquals(name, "cat");
System.out.println("is cat");
}
}
這會輸出:
is cat
is dog
其他註解
@EnabledOnJre(JAVA_19)
只在 JRE 19 環境運行,否則運行會輸出:Disabled on JRE version: xxx
.
@RepeatedTest(10)
重複測試,參數 10 可以讓單元測試重複運行 10 次。
JUnit 5 常用斷言
在上面的例子中,已經用到了 assertEquals
來判斷結果是否符合預期,assertEquals
是類 org.junit.jupiter.api.Assertions
中的一個方法;除此之外,還幾乎包括了所有我們日常測試想要用到的判斷方法。
下面是一些演示:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class JunitAssert {
@DisplayName("是否是狗")
@Test
public void testIsDog() {
String name = "dog";
Assertions.assertNotNull(name);
Assertions.assertEquals(name, "dog");
Assertions.assertNotEquals(name, "cat");
Assertions.assertTrue("dog".equals(name));
Assertions.assertFalse("cat".equals(name));
}
@DisplayName("是否是貓")
@Test
public void testIsCat() {
String name = "cat";
Assertions.assertNull(name, "name is not null");
}
}
在 testIsDog
中演示了一些常用的判斷方法,且都可以通過驗證。在 testIsCat
方法中進行了 null
值判斷,顯然這裏無法通過測試,會拋出自定義異常 name is not null
。
這會輸出:
org.opentest4j.AssertionFailedError: name is not null ==>
Expected :null
Actual :cat
<Click to see difference>
預期是一個 null
值,實際上是一個 cat
字符串。
Maven JUnit 測試
在 Maven 中進行 JUnit 測試,可以通過命令 mvn test
開始測試,默認情況下會測試所有依賴了當前源碼的 JUnit 測試用例。
準備被測 Preson類放在 src.main.java.com.wdbyte.test.junit5
.
package com.wdbyte.test.junit5;
public class Person {
public int getLuckyNumber() {
return 7;
}
}
編寫測試類 PersonTest 放在 src.test.java.com.wdbyte.test.junit5
. 這裏判斷獲取到的幸運數字是否是 8 ,明顯方法返回的是 7 ,所以這裏是測試會報錯。
package com.wdbyte.test.junit5;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("測試 Presion")
class PersonTest {
@DisplayName("測試幸運數字")
@Test
void getLuckyNumber() {
Person person = new Person();
Assertions.assertEquals(8, person.getLuckyNumber());
}
}
在 pom.xml 中引入 maven junit 測試依賴插件。
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
執行測試命令:mvn test
➜ junit5-jupiter-starter git:(master) ✗ mvn test
[INFO] Scanning for projects...
[INFO] ....
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.wdbyte.test.junit5.PersonTest
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.031 s <<< FAILURE! - in com.wdbyte.test.junit5.PersonTest
[ERROR] getLuckyNumber Time elapsed: 0.026 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <8> but was: <7>
at com.wdbyte.test.junit5.PersonTest.getLuckyNumber(PersonTest.java:18)
[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR] PersonTest.getLuckyNumber:18 expected: <8> but was: <7>
[INFO]
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.777 s
[INFO] Finished at: 2022-11-17T23:01:09+08:00
[INFO] ------------------------------------------------------------------------
也可以指定類進行測試:mvn -Dtest=PersonTest test
一如既往,文章中代碼存放在 Github.com/niumoo/javaNotes.
<完>
文章持續更新,可以微信搜一搜「 程序猿阿朗 」或訪問「程序猿阿朗博客 」第一時間閱讀。本文 Github.com/niumoo/JavaNotes 已經收錄,有很多知識點和系列文章,歡迎Star。