JUnit5簡介
Spring Boot 2.2.0 版本開始引入 JUnit 5 作為單元測試默認庫
作為最新版本的JUnit框架,JUnit5與之前版本的JUnit框架有很大的不同。由三個不同子項目的幾個不同模塊組成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: Junit Platform是在JVM上啟動測試框架的基礎,不僅支持Junit自制的測試引擎,其他測試引擎也都可以接入。
- JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的編程模型,是JUnit5新特性的核心。內部包含了一個測試引擎,用於在Junit Platform上運行。
- JUnit Vintage: 由於JUint已經發展多年,為了照顧老的項目,JUnit Vintage提供了兼容JUnit4.x,JUnit3.x的測試引擎。

注意:
- SpringBoot 2.4 以上版本移除了默認對 Vintage 的依賴。如果需要兼容JUnit4需要自行引入(不能使用JUnit4的功能 @Test)。
- JUnit 5’s Vintage已經從spring-boot-starter-test從移除。如果需要繼續兼容Junit4需要自行引入Vintage依賴:
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
- 使用添加JUnit 5,添加對應的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
JUnit的基本單元測試模板
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;//注意不是org.junit.Test(這是JUnit4版本的)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringBootApplicationTests {
@Autowired
private Component component;
@Test
//@Transactional 標注后連接數據庫有回滾功能
public void contextLoads() {
Assertions.assertEquals(5, component.getFive());
}
}
常用注解
-
SpringBootTest: 表示使用spring做為測試驅動
-
**@Test : **表示方法是測試方法。
-
@ParameterizedTest : 表示方法是參數化測試,下方會有詳細介紹
-
@RepeatedTest(n) : 表示方法可重復執行n次
-
**@DisplayName : **給測試類或者測試方法設置展示名稱
-
@BeforeEach :表示在每個單元測試之前執行
-
@AfterEach :表示在每個單元測試之后執行
-
@BeforeAll :表示在所有單元測試之前執行
-
@AfterAll :表示在所有單元測試之后執行
-
@Tag :表示單元測試類別,類似於JUnit4中的@Categories
-
@Disabled : 表示測試類或測試方法不執行,類似於JUnit4中的@Ignore
-
@Timeout :表示測試方法運行如果超過了指定時間將會返回錯誤
-
@ExtendWith : 為測試類或測試方法提供擴展類引用
@SpringBootTest
@DisplayName("junit5功能測試類")
public class JUnit5Test {
@Test
void test1(){
System.out.println("測試方法1");
}
@Disabled
@DisplayName("測試方法2")
@Test
void test2() {
System.out.println(2);
}
@RepeatedTest(5)
@Test
void test3() {
System.out.println(5);
}
/**
* 規定方法超時時間。超出則報異常
*
* @throws InterruptedException
*/
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(100);
}
@BeforeEach
void testBeforeEach() {
System.out.println("@BeforeEach 測試就要開始了...");
}
@AfterEach
void testAfterEach() {
System.out.println("@AfterEach 測試結束了...");
}
@BeforeAll
static void testBeforeAll() {
System.out.println("@BeforeAll 所有測試就要開始了...");
}
@AfterAll
static void testAfterAll() {
System.out.println("@AfterAll 所有測試以及結束了...");
}
}
@BeforeEach
和@AfterEach
每個測試方法執行時都會生效。@BeforeAll
和@AfterAll
只會生效一次
斷言機制
斷言Assertion是測試方法中的核心部分,用來對測試需要滿足的條件進行驗證。這些斷言方法都是org.junit.jupiter.api.Assertions的靜態方法。檢查業務邏輯返回的數據是否合理。所有的測試運行結束以后,會有一個詳細的測試報告。
JUnit 5 內置的斷言可以分成如下幾個類別:
方法 | 說明 |
---|---|
assertEquals | 判斷兩個對象或兩個原始類型是否相等 |
assertNotEquals | 判斷兩個對象或兩個原始類型是否不相等 |
assertSame | 判斷兩個對象引用是否指向同一個對象 |
assertNotSame | 判斷兩個對象引用是否指向不同的對象 |
assertTrue | 判斷給定的布爾值是否為 true |
assertFalse | 判斷給定的布爾值是否為 false |
assertNull | 判斷給定的對象引用是否為 null |
assertNotNull | 判斷給定的對象引用是否不為 null |
簡單斷言
@Test
@DisplayName("simple assertion")
public void simple() {
assertEquals(3, 1 + 2, "simple math");
assertNotEquals(3, 1 + 1);
assertNotSame(new Object(), new Object());
Object obj = new Object();
assertSame(obj, obj);
assertFalse(1 > 2);
assertTrue(1 < 2);
assertNull(null);
assertNotNull(new Object());
}
數組斷言
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
組合斷言
assertAll()
方法接受多個 org.junit.jupiter.api.Executable
函數式接口的實例作為要驗證的斷言,且所有斷言都需通過,可以通過 lambda 表達式很容易的提供這些斷言。
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
異常斷言
在JUnit4時期,想要測試方法的異常情況時,需要用@Rule
注解的ExpectedException
變量還是比較麻煩的。而JUnit5提供了一種新的斷言方式Assertions.assertThrows()
,配合函數式編程就可以進行使用。
@Test
@DisplayName("異常測試")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//拋出指定異常才測試通過
ArithmeticException.class, () -> System.out.println(1 % 0));
}
超時斷言
JUnit5還提供了Assertions.assertTimeout()
為測試方法設置了超時時間。
@Test
@DisplayName("超時測試")
public void timeoutTest() {
//如果測試方法執行時間超過1s將會異常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
快速失敗
通過 fail 方法直接使得測試失敗。
@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}
前置條件
JUnit 5 中的前置條件assumptions
(假設)類似於斷言,不同之處在於不滿足的斷言會使得測試方法失敗,而不滿足的前置條件只會使得測試方法的執行終止。前置條件可以看成是測試方法執行的前提,當該前提不滿足時,就沒有繼續執行的必要。
@DisplayName("前置條件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("simple")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assume then do")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
- assumeTrue 和 assumFalse 確保給定的條件為 true 或 false,不滿足條件會使得測試執行終止。
- assumingThat 的參數是表示條件的布爾值和對應的 Executable 接口的實現對象。只有條件滿足時,Executable 對象才會被執行;當條件不滿足時,測試執行並不會終止。
嵌套測試
JUnit 5 可以通過 Java 中的內部類和@Nested
注解實現嵌套測試,從而可以更好的把相關的測試方法組織在一起。在內部類中可以使用@BeforeEach
和@AfterEach
注解,而且嵌套的層次沒有限制。
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
參數化測試
參數化測試是JUnit5很重要的一個新特性,它使得用不同的參數多次運行測試成為了可能,也為我們的單元測試帶來許多便利。利用@ValueSource等注解,指定入參,我們將可以使用不同的參數進行多次單元測試,而不需要每新增一個參數就新增一個單元測試,省去了很多冗余代碼。
- @ValueSource: 為參數化測試指定入參來源,支持八大基礎類以及String類型、Class類型
- @NullSource: 表示為參數化測試提供一個null的入參
- @EnumSource: 表示為參數化測試提供一個枚舉入參
- @CsvFileSource:表示讀取指定CSV文件內容作為參數化測試入參
- @MethodSource:表示讀取指定方法的返回值作為參數化測試入參(注意方法返回需要是一個流)
當然如果參數化測試僅僅只能做到指定普通的入參還達不到讓我覺得驚艷的地步。讓我真正感到他的強大之處的地方在於他可以支持外部的各類入參。如:CSV,YML,JSON 文件甚至方法的返回值也可以作為入參。只需要去實現ArgumentsProvider接口,任何外部文件都可以作為它的入參。
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("參數化測試1")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
@ParameterizedTest
@MethodSource("method") //指定方法名
@DisplayName("方法來源參數")
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}
static Stream<String> method() {
return Stream.of("apple", "banana");
}