開始看內容之前,咱先說點有趣的事,就是小黑晚上剛剛寫完這篇文章,這不是不知道該起什麼標題嘛,於是請教了一下掘友兄弟們,接著就出現了下面這一幕
我直接麻了呀掘友們真的是個人是人才,說話又好聽呀!!嗯,都有責任,雪崩的時候,每一片雪花都在勇闖天涯。
什麼是Junit5
Junit是Java語言中的一個流行測試框架,是由Kent Beck和Erich Gamma開發的。它的第一個版本於1997年釋出。由於其易用性,它成為Java社群中最流行的測試框架之一。它是一個輕量級測試框架,允許Java開發人員用Java語言編寫單元測試用例。最新發布的版本是5.8.2,被稱為JUnit5。
JUnit 5由許多不同的模組組成。主要包括以下三個子模組:
- Junit Platform
- Junit Jupiter
- Junit Vintage
以上三個模組構成了Junit5的核心功能。
Junit 5的架構
Junit5包含以下三部分核心元件:
Junit Platform
該模組提供了在JVM上啟動測試框架的核心基礎功能,充當JUnit與其客戶端(如構建工具[Maven、Gradle]和IDE[Eclipse、IntelliJ])之間的介面。它引入了Launcher(啟動器)的概念,外部工具可以使用它來發現、過濾和執行測試用例。
它還提供了TestEngine API,用於開發在JUnit上執行的測試框架;使用TestEngine API,第三方測試庫(如Spock、Cucumber和FitNesse)可以直接j整合它們的自定義TestEngine。
Junit Jupiter
該模組為在Junit 5中編寫測試和擴充套件提供了一個新的程式設計模型和擴充套件模型。
它有一套全新的註解來編寫Junit5中的測試用例,其中包括@BeforeEach、@AfterEach、@AfterAll、@BeforeAll等。可以理解為是對Junit Platform的TestEngine API的實現,以便Junit5測試可以執行。
Junit Vintage
Vintage從字面意思理解是“古老的,經典的”。
該模組就是為了對Junit4和JUnit3編寫的測試用例提供支援。因此,Junit5具備向後相容能力。
快速入門
在我們開始編寫Junit5測試之前,需要先具備以下條件。
Java 8+
Junit 5要求JDK版本最低是Java 8,所以我們需要先安裝Java 8或更高版本的JDK。
IDE
我們肯定不能直接在記事本里編寫程式碼,所以需要使用順手的IDE,我因為習慣於使用IntelliJ IDEA,所以接下來的示例都是在IDEA中進行。
如果你更喜歡使用Eclipse,可以使用Eclipse Oxygen版本或者更高階的版本。
因為在Junit5的學習過程中不會用到特別多的Jar包依賴,所以這裡先不使用Maven或Gradle構建工具。
接下來我們開始在IDEA中編寫測試程式碼。
首先,在IDE中建立一個Java專案,JDK選擇1.8版本。
然後一直點選Next,給我們的工程起一個名字,就叫Junit5吧。
接下來,在我們的專案中建一個test資料夾,並新建一個測試類FirstJunit5Test.java
現在就可以編寫第一個測試用例啦。
package test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author 小黑說Java
* @ClassName FirstJunit5Test
* @Description
* @date 2022/1/6
**/
public class FirstJunit5Test {
@Test
public void test() {
fail("還沒有實現的測試用例");
}
}
複製程式碼
正常情況下,你在寫這段程式碼時,會編譯失敗,因為我們的工程中現在還沒有新增Junit5的Jar包。
透過程式碼提示,將Junit5的Jar包新增到classpath就可以了。
在我們上面的程式碼中,有一個test()方法,該方法上有一個@Test註解,表示這是一個測試方法,我們在這個方法中編寫程式碼進行測試。
最後直接右鍵執行該測試方法。測試用例失敗,如下面的圖所示。它給出“AssertionFailedError:還沒有實現的測試用例”。
這是因為在test()方法中,是使用fail("還沒有實現的測試用例")斷言。該斷言未透過測試用例。
@Test註解
該註解是我們在編寫測試用例時最常使用的一個註解。
接下來,我們先定義一個具有功能的類,然後透過測試用例來對功能進行不同場景的測試。
package test;
/**
* @author 小黑說Java
* @ClassName OddEven
**/
public class OddEven {
/**
* 判斷一個數是否為偶數
*/
public boolean isNumberEven(int number) {
return number % 2 == 0;
}
}
複製程式碼
很簡單的一個功能,在OddEven類中有一個isEvenNumber()方法,用來判斷一個數字是奇數還是偶數。如果為偶數返回true,反之返回false。
接下來我們編寫測試程式碼。 為了測試isEvenNumber()方法,我們需要編寫覆蓋其功能的測試用例。
- 傳入偶數,它應該返回true;
- 傳入奇數,應該返回false。
為了將測試類中建立的方法識別為測試方法,我們需要使用@Test註釋對其進行標記。讓我們來看看測試類和測試方法:
package test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author 小黑說Java
* @ClassName OddEvenTest
* @Description
* @date 2022/1/6
**/
public class OddEvenTest {
@Test
void evenNumberTrue() {
OddEven oddEven = new OddEven();
assertTrue(oddEven.isNumberEven(10));
}
@Test
void oddNumberFale() {
OddEven oddEven = new OddEven();
assertFalse(oddEven.isNumberEven(11));
}
}
複製程式碼
在以上程式碼中,我們使用到了Junit5的斷言assertTrue()和assertFasle();
assertTrue()方法接受布林值並確保該值為true。如果傳遞的是false,則測試用例將失敗。
assertFalse()方法接受布林值並確保該值為false。如果傳遞的值為true,則測試用例將失敗。
執行上面的測試用例,會得到如下結果,表示測試用例透過。
什麼是斷言?
如果你不理解斷言的字面意思,可以看一下翻譯,asserts表示明確肯定。
沒錯,小黑哥不光講技術,還教英語~
在JUnit5中斷言的作用就是幫助我們用測試用例的實際輸出驗證預期輸出。
簡而言之,斷言是我們在測試中用來驗證預期行為的靜態方法。
所有JUnit5的斷言都在org.juit.jupiter.Assertions類中。這些方法支援Java8 lambda表示式,並被大量過載以支援不同型別,如基本資料型別、物件、stream、陣列等。
斷言方法
斷言方法 |
作用 |
assertNull() |
斷言實際輸出為null. |
assertNotNull() |
斷言實際輸出不為null. |
fail() |
讓測試用例不透過 |
assertSame() |
斷言期望值和實際值是用一個物件 |
assertNotSame() |
斷言期望值和實際值不是用一個物件 |
assertTrue() |
斷言實際值為true |
assertFalse() |
斷言實際值為false |
assertEquals() |
斷言期望值和實際值相等 |
assertNotEquals() |
斷言期望值和實際值不相等 |
assertArrayEquals() |
斷言期望陣列和實際陣列相等 |
assertIterableEquals() |
斷言期望可迭代容器和實際可迭代容器相等 |
assertThrows() |
斷言可執行程式碼中會丟擲期望的異常型別 |
assertAll() |
斷言一組中的多個 |
assertTimeout() |
斷言一段可執行程式碼的會在指定時間執行結束 |
assertTimeoutPreemptively() |
斷言可執行程式碼如果超過指定時間會被搶佔中止 |
在上一節內容中我們初步使用了fail(),assertTrue(), assertFalse(),接下來我們重點介紹一下其他幾個斷言。
assertNull()
該斷言方法幫助我們驗證特定物件是否為空。
- 如果實際值為空,則測試用例將透過
- 如果實際值不為空,則測試用例將失敗
assertNull()有三種過載方法,如下所述:
public static void assertNull(Object actual)
public static void assertNull(Object actual, String message)
public static void assertNull(Object actual, Supplier<String> messageSupplier)
複製程式碼
- AssertNull(Object Actual)-它斷言實際值是否為空。
- AssertNull(Object Actual,String Message)-它斷言實際值是否為空。在這種情況下,如果實際值不為空,則測試用例將失敗,並顯示一條提供的訊息。
- AssertNull(Object Actual,Supplier messageSupplier)-它斷言實際值是否為空。在這種情況下,如果實際值不為空,則測試用例將失敗,並透過供應商功能提供一條訊息。使用Supplier函式的主要優點是,只有在測試用例失敗時,它才懶惰地計算為字串。
接下來我們編寫一個程式碼案例。假設我們現在有一個字串工具類StringUtils,該類中提供一個reverse(String)方法,可實現字串反轉功能。
package com.heiz123.junit5;
/**
* @author 小黑說Java
* @ClassName StringUtils
* @Description
* @date 2022/1/6
**/
public class StringUtils {
public static String reverse(String input) {
if (input == null) {
return null;
}
if (input.length() == 0) {
return "";
}
char[] charArray = input.toCharArray();
int start = 0;
int end = input.length() - 1;
while (start < end) {
char temp = charArray[start];
charArray[start] = charArray[end];
charArray[end] = temp;
start++;
end--;
}
return new String(charArray);
}
}
複製程式碼
我們使用assertNull()斷言來對reverse()方法編寫如下測試用例。
- 如果我們以“ABC”的形式提供輸入字串,它將返回“CBA”
- 如果我們提供的輸入字串為null,則返回null
- 如果我們將輸入字串作為“”提供,它將返回“”字串
編寫一個測試類StringUtilsTest,程式碼如下:
package test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.function.Supplier;
import com.heiz123.junit5.StringUtils;
import org.junit.jupiter.api.Test;
/**
* @author 小黑說Java
* @ClassName StringUtilsTest
* @Description
* @date 2022/1/6
**/
class StringUtilsTest {
@Test
void nullStringRevered() {
String actual = StringUtils.reverse((null));
assertNull(actual);
}
@Test
void emptyStringReversed() {
String actual = StringUtils.reverse((""));
String message = "Actual String should be null !!! ";
assertNull(actual, message);
}
@Test
void NonNullStringReversed() {
String actual = StringUtils.reverse(("ABC"));
Supplier<String> messageSupplier = () -> "Actual String should be null !!! ";
// assertNull使用Java 8的MessageSupplier
assertNull(actual, messageSupplier);
}
}
複製程式碼
執行測試用例結果我們發現,只有nullStringRevered()測試用例透過。
如果你多次執行測試用例會發現執行順序並不固定。
- 在StringUtilsTest類中有3個@Test方法: nullStringRevered():當向reverse()方法傳入null時,則返回null。因此,assertNull()會斷言實際返回的值為空。它通過了Junit測試用例。
- emptyStringReversed():當向reverse()方法傳入""時,則返回""。這裡返回值為空字串,不為空。因此,它無法透過Junit測試用例。在此測試用例中,我們使用過載的assertNull()方法,該方法將字串訊息作為第二個引數。因為這個測試用例不滿足斷言條件,所以它失敗,並給出“AssertionFailedError: Actual String should be null !!! ==> Expected :null Actual :”。
- NonNullStringReversed:當向reverse()方法傳入"ABC"時,返回"CBA"。這裡,返回值不為空。因此,它無法透過Junit測試用例。在此測試用例中,使用過載的assertNull()方法,該方法將Supplier<String>messageSupplier作為第二個引數。因為此測試用例不滿足斷言條件,所以它失敗,並丟擲“AssertionFailedError: Actual String should be null !!! ==> Expected :null Actual :CBA”。
assertThrows()
該斷言方法有助於斷言用於驗證一段程式碼中是否丟擲期望的異常。
- 如果沒有引發異常,或者引發了不同型別的異常,則此方法將失敗;
- 它遵循繼承層次結構,因此如果期望的型別是Exception,而實際是RuntimeException,則斷言也會透過。
assertThrow()也有三種有用的過載方法:
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable)
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable, String message)
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable, Supplier<String> messageSupplier)
複製程式碼
我們透過以下測試程式碼來看一下這個斷言:
package test;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* @author 小黑說Java
* @ClassName AssertThrowTest
* @Description
* @date 2022/1/6
**/
public class AssertThrowTest {
@Test
public void testAssertThrows() {
assertThrows(ArithmeticException.class, () -> divide(1, 0));
}
@Test
public void testAssertThrowsWithMessage() {
assertThrows(IOException.class, () -> divide(1, 0), "除以0啦!!!");
}
@Test
public void testAssertThrowsWithMessageSupplier() {
assertThrows(Exception.class, () -> divide(1, 0), () -> "除以0啦!!!");
}
private int divide(int a, int b) {
return a / b;
}
}
複製程式碼
在以上測試程式碼中,三個測試用例都使用assertThrows(),分別期望不同的異常型別,對應的可執行程式碼為呼叫divide方法,用1除以0。
執行該測試程式碼結果如下:
- testAssertThrows() : 該用例期望丟擲ArithmeticException,因為1除以0丟擲的異常就是ArithmeticException,所以該用例透過;
- testAssertThrowsWithMessage():該用例期望丟擲IOException,ArithmeticException並不是IOException的子類,所以測試未透過;
- testAssertThrowsWithMessageSupplier():該用例期望丟擲Exception,ArithmeticException是Exception的自雷,所以測試透過。
使用Supplier<String> messageSupplier引數的斷言相比使用String message引數斷言有一個優點,就是隻有在斷言不透過的時候,才會構造字串物件。
assertTimeout()
該斷言方法它用於測試長時間執行的任務。
如果測試用例中的給定任務花費的時間超過指定時間,則測試用例將失敗。
提供給測試用例的任務將與呼叫程式碼在同一個執行緒中執行。此外,如果超時,並不會搶先中止任務的執行。
該方法同樣有三個過載實現:
public static void assertTimeout(Duration timeout, Executable executable)
public static void assertTimeout(Duration timeout, Executable executable, String message)
public static void assertTimeout(Duration timeout, Executable executable, Supplier<String> messageSupplier)
複製程式碼
透過以下測試程式碼來看一下該斷言方法如何執行:
package test;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTimeout;
/**
* @author 小黑說Java
* @ClassName AssertTimeoutTest
* @Description
* @date 2022/1/6
**/
public class AssertTimeoutTest {
@Test
void timeoutNotExceeded() {
// 該斷言成功
assertTimeout(Duration.ofMinutes(3), () -> {
// 執行不到3分鐘的任務
});
}
@Test
void timeoutNotExceededWithResult() {
// 該斷言執行成功並返回物件
String actualResult = assertTimeout(Duration.ofMinutes(3), () -> {
return "result";
});
assertEquals("result", actualResult);
}
@Test
void timeoutNotExceededWithMethod() {
// 該斷言呼叫一個方法引用並返回一個物件
String actualGreeting = assertTimeout(Duration.ofMinutes(3), AssertTimeoutTest::greeting);
assertEquals("Hello, World!", actualGreeting);
}
@Test
void timeoutExceeded() {
// 以下斷言失敗,並顯示類似於以下內容的錯誤訊息:
// execution exceeded timeout of 10 ms by 91 ms
assertTimeout(Duration.ofMillis(10), () -> {
// 模擬耗時超過10毫秒的任務
Thread.sleep(100);
System.out.println("結束");
});
}
private static String greeting() {
return "Hello, World!";
}
}
複製程式碼
執行以上測試用例結果如下:
除了timeoutExceeded()其他測試用例都透過。並且從日誌中可以看到,有打印出“結束”,說明並未將任務執行執行緒中斷。
assertTimeoutPreemptively()
該斷言方法和assertTimeout()作用基本相同,但是有一個主要的區別,使用該斷言提供給測試用例的任務將在與呼叫程式碼不同的執行緒中執行。並且,如果執行超時,任務的執行將被搶先中止。
package test;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import java.time.Duration;
import org.junit.jupiter.api.Test;
/**
* @author 小黑說Java
* @ClassName AssertTimeoutPreemptivelyTest
* @Description
* @date 2022/1/6
**/
public class AssertTimeoutPreemptivelyTest {
@Test
void timeoutExceededWithPreemptiveTermination() {
// 以下斷言失敗,並顯示類似於以下內容的錯誤訊息:
// execution timed out after 10 ms
assertTimeoutPreemptively(Duration.ofMillis(10), () -> {
// 模擬耗時超過10毫秒的任務
Thread.sleep(100);
System.out.println("結束");
});
}
}
複製程式碼
執行該測試案例結果如下,可以看到,並沒有打印出“結束”。
斷言是org.juit.jupiter.Assertions類中的一系列靜態方法,其作用就是在測試中用來驗證預期行為。
什麼是“假設”?
在JUnit5中的org.junit.jupiter.api.Assumptions類中定義了一系列的支援基於假設的條件執行的測試方法。
與斷言相比,如果假設方法失敗並不會導致測試用例的失敗,只會讓測試用例中止。
假設通常在中斷沒有意義的測試方法時使用。例如,如果測試依賴於當前執行時環境中並不存在的內容,那麼這個測試案例就沒有測試意義。
如果假設不滿足,則會丟擲TestAbortedException。
Junit 5中有3種類型的假設:
assumeTrue() : 假設為真。
assumeFalse(): 假設為假。
assumeThat(): 假設是某種特定情況。
assumeTrue()
該方法假設給定的引數是true,如果傳入的引數為true,則測試繼續執行;如果假設為false,則該測試方法中止。
assumeTrue()有三類型別的過載方法:
// 使用boolean作為假設驗證
public static void assumeTrue(boolean assumption) throws TestAbortedException
public static void assumeTrue(boolean assumption, Supplier<String> messageSupplier) throws TestAbortedException
public static void assumeTrue(boolean assumption, String message) throws TestAbortedException
// 使用BooleanSupplier作為假設驗證
public static void assumeTrue(BooleanSupplier assumptionSupplier) throws TestAbortedException
public static void assumeTrue(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException
public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier<String> messageSupplier) throws TestAbortedException
複製程式碼
接下來我們看一下上面的方法如何使用:
package test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
/**
* @author 小黑說Java
* @ClassName AssumeTrueTest
* @Description
* @date 2022/1/6
**/
public class AssumeTrueTest {
@Test
void testOnDevelopmentEnvironment() {
System.setProperty("ENV", "DEV");
assumeTrue("DEV".equals(System.getProperty("ENV")));
//後續程式碼會繼續執行
System.out.println("開發環境測試");
}
@Test
void testOnProductionEnvironment() {
System.setProperty("ENV", "PROD");
assumeTrue("DEV".equals(System.getProperty("ENV")), "假設失敗");
// 後續程式碼不會執行
System.out.println("生產環境測試");
}
}
複製程式碼
執行結果如下,testOnProductionEnvironment()因為假設結果為false,所以中斷,並沒有列印輸出語句。
assumeFalse()
assumeFalse()和assumeTrue()同理,驗證給定的假設是否為false,如果為false,則測試繼續,如果為true,測試中止。
assumingThat()
假設JUnit5中的API有一個靜態實用程式方法,名為AssergingThat()。 該假設方法接收一個Boolean或BooleanSupplier作為假設,對該假設進行驗證,如果驗證透過,則執行第二個引數傳入的Executable;如果假設驗證不透過,則不執行;不管是否驗證透過,都不會影響測試用例後續的執行。
public static void assumingThat(boolean assumption, Executable executable)
public static void assumingThat(BooleanSupplier assumptionSupplier, Executable executable)
複製程式碼
我們透過下面程式碼來驗證一下該假設方法:
package test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumingThat;
/**
* @author 小黑說Java
* @ClassName AssumingThatTest
* @Description
* @date 2022/1/6
**/
public class AssumingThatTest {
@Test
void testInAllEnvironments() {
System.setProperty("ENV", "DEV");
assumingThat("DEV".equals(System.getProperty("ENV")),
() -> {
System.out.println("testInAllEnvironments - 只在開發環境執行!!!");
assertEquals(2, 1 + 1);
});
assertEquals(42, 40 + 2);
}
@Test
void testInAllEnvironments2() {
System.setProperty("ENV", "DEV");
assumingThat("PROD".equals(System.getProperty("ENV")),
() -> {
System.out.println("testInAllEnvironments2 - 只在生產環境執行 !!!");
assertEquals(2, 1 + 1);
});
assertEquals(42, 40 + 2);
}
}
複製程式碼
執行結果如下,可以看到只有testInAllEnvironments()中的Executable執行了:
生命週期相關注解
在Junit5中,對於每個@Test,都會建立一個測試類的新例項。例如,如果一個類有兩個@Test方法,那麼將建立兩個測試類例項,每個@Test一個。因此,測試類的建構函式被呼叫的次數與@Test方法的數量一樣多。
我們可以透過如下程式碼來證明這一點:
package test;
import org.junit.jupiter.api.Test;
/**
* @author 小黑說Java
* @ClassName LifecycleTest
* @Description
* @date 2022/1/6
**/
public class LifecycleTest {
public LifecycleTest() {
System.out.println("LifecycleTest - 建立例項 !!!");
}
@Test
public void testOne() {
System.out.println("LifecycleTest - testOne()執行!!!");
}
@Test
public void testTwo() {
System.out.println("LifecycleTest - testTwo()執行!!!");
}
}
複製程式碼
從以下執行結果我們可以看出,確實執行了兩次構造方法。這表明每個測試方法都是在各自的測試物件例項中執行。
@BeforeEach和@AfterEach
顧名思義,使用@BeforeEach和@AfterEach註釋的方法在每個@Test方法之前和之後呼叫。因此,如果測試類中有兩個@Test方法,則@BeforeEach方法將在測試方法之前被呼叫兩次,@AfterEach方法同理。
package test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* @author 小黑說Java
* @ClassName LifecycleTest
* @Description
* @date 2022/1/6
**/
public class LifecycleTest {
public LifecycleTest() {
System.out.println("LifecycleTest - 建立例項 !!!");
}
@BeforeEach
public void beforeEach() {
System.out.println("LifecycleTest - beforeEach()執行!!!");
}
@Test
public void testOne() {
System.out.println("LifecycleTest - testOne()執行!!!");
}
@Test
public void testTwo() {
System.out.println("LifecycleTest - testTwo()執行!!!");
}
@AfterEach
public void afterEach() {
System.out.println("LifecycleTest - afterEach()執行 !!!");
}
}
複製程式碼
執行結果如下:
因為Junit測試類有兩個@Test方法,所以它在測試類的單獨例項中執行每個測試方法。隨機選擇一個@Test方法,按照如下順序執行:
- 執行構造方法建立例項;
- 執行@BeforeEach註解方法;
- 執行@Test方法;
- 執行@AfterEach註解方法。
然後,再按照相同的步驟執行下一個@Test方法。
通常,當我們擁有跨各種測試用例的公共設定邏輯時,公共初始化程式碼放在@BeforeEach方法中,並在@AfterEach方法中進行清理。
@BeforeAll和@AfterAll
顧名思義,@BeforeAll是在所有測試用例執行之前執行,@AfterAll是在所有測試用例執行之後執行。
這兩個註解可@BeforeEach和@AfterEach還有一點不同,就是隻能修改在靜態方法上。這裡也很好理解,因為每個@Test方法都是單獨的測試物件,要在所有用例前執行的方法必然不能是某一個物件的方法。
package test;
import org.junit.jupiter.api.*;
/**
* @author 小黑說Java
* @ClassName LifecycleTest
* @Description
* @date 2022/1/6
**/
public class LifecycleTest {
@BeforeAll
public static void beforeAll() {
System.out.println("LifecycleTest - beforeAll()執行!!!");
}
public LifecycleTest() {
System.out.println("LifecycleTest - 建立例項 !!!");
}
@BeforeEach
public void beforeEach() {
System.out.println("LifecycleTest - beforeEach()執行!!!");
}
@Test
public void testOne() {
System.out.println("LifecycleTest - testOne()執行!!!");
}
@Test
public void testTwo() {
System.out.println("LifecycleTest - testTwo()執行!!!");
}
@AfterEach
public void afterEach() {
System.out.println("LifecycleTest - afterEach()執行 !!!");
}
@AfterAll
public static void afterAll() {
System.out.println("LifecycleTest - afterAll()執行!!!");
}
}
複製程式碼
執行以上程式碼結果如下:
@BeforeAll和@Afterall具有以下特點:
- 這兩種方法都是靜態方法。
- 這兩種方法在測試生命週期中都只會呼叫一次;
- @BeforeAll方法是類級方法,會在建構函式之前被呼叫;
- @AfterAll方法也是類級別的方法,它在所有方法執行後被呼叫。
- @BeforeAll方法常用於需要進行資源初始化的地方,比如資料庫連線、伺服器啟動等。這些資源可以被測試方法使用。
- @AfterAll方法常用於需要進行昂貴的資源清理的地方,比如資料庫連線關閉、伺服器停止等。
自定義名稱@DisplayName
JUnit5中的@DisplayName註解用於為測試類自定義名稱。預設情況下,JUnit5測試報告會在IDE測試報告中和在執行測試用例時列印測試類的類名。我們可以使用@DisplayName註解為測試類提供自定義名稱,這使其更易於閱讀。
@DisplayName不僅可以放在類上,我們還可以加在測試方法上,給每個測試用例自定義名稱。
@DisplayName註解可以接受具有以下內容的字串:
- 一串單詞;
- 特殊字元;
- 甚至可以使用表情符號。
@DisplayName("$測試 DisplayName %^&")
public class DisplayNameTest {
@Test
@DisplayName("$用例1%^&")
public void test() {
System.out.println("test method執行!!!");
}
}
複製程式碼
引數解析器ParameterResolver
在Junit5之前的版本中,對在測試類的構造方法或測試方法中使用引數的支援比較有限。
JUnit5的Jupiter中的一個主要變化是現在允許測試建構函式和測試方法都有引數。這些引數為建構函式和測試方法提供元資料。
因此,可以更靈活的支援測試方法和建構函式的依賴項注入。
在JUnit 5中,org.juit.jupiter.api.tension包中有一個名為ParameterResolver的介面。該介面定義了希望在執行時動態解析引數的測試擴充套件的API。
Junit 5中有多種型別的ParameterResolver。通常,如果測試建構函式或@Test、@BeforeEach、@AfterEach、@BeforeAll、@AfterAll、@TestFactory方法接受引數,則必須在執行時由註冊的ParameterResolver解析該引數。
TestInfoParameterResolver
TestInfoParameterResolver是一個內建的ParameterResolver,如果方法引數的型別為TestInfo,則表示TestInfoParameterResolver將提供與當前測試對應的TestInfo例項作為該引數的值。然後,可以使用作為引數的TestInfo來檢索有關當前測試的資訊或元資料,例如測試的顯示名稱、測試類、測試方法或關聯的標記。 如果在測試方法上使用@DisplayName註釋,則檢索與測試方法關聯的自定義名稱,否則檢索技術名稱,即測試類或測試方法的實際名稱。
package test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
/**
* @author 小黑說Java
* @ClassName TestInfoParameterTest
* @Description
* @date 2022/1/6
**/
@DisplayName("測試Parameter")
public class TestInfoParameterTest {
@BeforeAll
public static void beforeAll(TestInfo testInfo) {
System.out.println("beforeAll() 執行- ");
System.out.println("Display name - " + testInfo.getDisplayName());
System.out.println("Test Class - " + testInfo.getTestClass());
System.out.println("Test Method - " + testInfo.getTestMethod());
System.out.println("*******************************************");
}
public TestInfoParameterTest(TestInfo testInfo) {
System.out.println("Constructor 執行 - ");
System.out.println("Display name - " + testInfo.getDisplayName());
System.out.println("Test Class - " + testInfo.getTestClass());
System.out.println("Test Method - " + testInfo.getTestMethod());
System.out.println("*******************************************");
}
@BeforeEach
public void beforeEach(TestInfo testInfo) {
System.out.println("beforeEach()執行 - ");
System.out.println("Display name - " + testInfo.getDisplayName());
System.out.println("Test Class - " + testInfo.getTestClass());
System.out.println("Test Method - " + testInfo.getTestMethod());
System.out.println("*******************************************");
}
@Test
@DisplayName("測試用例1")
public void testOne(TestInfo testInfo) {
System.out.println("testOne() got executed with test info as - ");
System.out.println("Display name - " + testInfo.getDisplayName());
System.out.println("Test Class - " + testInfo.getTestClass());
System.out.println("Test Method - " + testInfo.getTestMethod());
System.out.println("*******************************************");
}
}
複製程式碼
以上程式碼執行結果如下:
從結果可以看出,傳入TestInfo引數,可以在方法中獲取到對應的測試用例方法名,以及對應的DisplayName等資訊。
禁用測試用例
我們可能需要在某些情況下禁用我們的測試用例,在Junit5中提供了一禁用或啟用測試方法或測試類的能力。
@Disabled
該註解可以作用在測試類或測試方法上,如果在測試類上則表示該類下的所有測試方法都關閉;如果作用在方法上,表示該測試方法被關閉。
同時,該註解有一個可選引數,可以作為關閉測試用例的原因。
package test;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author 小黑說Java
* @ClassName DisableTest
* @Description
* @date 2022/1/6
**/
@Disabled
public class DisableTest {
@Test
void evenNumberTrue() {
OddEven oddEven = new OddEven();
assertTrue(oddEven.isNumberEven(10));
}
@Test
@Disabled("因為XXX關閉該用例")
void oddNumberFale() {
OddEven oddEven = new OddEven();
assertFalse(oddEven.isNumberEven(11));
}
}
複製程式碼
Junit5還提供了其他各種可以對測試用例進行禁用和啟用的註解,這裡不在重複講解。如果你有興趣可以透過Junit 5中jupiter官方API瞭解更多.
巢狀測試
@Nested註解可以支援對多個測試進行分組,來建立各個測試用例之間的關係。通常是在主測試類中新增內部類的方式來實現。
但是,預設情況下,內部類並不參與測試執行,只有使用@Nested註解修飾的內部類才能具備測試功能。
package test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.LinkedList;
import java.util.Queue;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author 小黑說Java
* @ClassName TestingAQueueTest
* @Description
* @date 2022/1/6
**/
public class TestingAQueueTest {
// 字串佇列
Queue<String> queue;
@Test
@DisplayName("is null")
void isNotInstantiated() {
assertNull(queue);
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
queue = new LinkedList<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(queue.isEmpty());
}
@Test
@DisplayName("return null element when polled")
void returnNullWhenPolled() {
assertNull(queue.poll());
}
@Test
@DisplayName("return null element when peeked")
void returnNullWhenPeeked() {
assertNull(queue.peek());
}
@Nested
@DisplayName("after offering an element")
class AfterOffering {
String anElement = "an element";
@BeforeEach
void offerAnElement() {
queue.offer(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(queue.isEmpty());
}
@Test
@DisplayName("returns the element when polled and is empty")
void returnElementWhenPolled() {
assertEquals(anElement, queue.poll());
assertTrue(queue.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, queue.peek());
assertFalse(queue.isEmpty());
}
}
}
}
複製程式碼
執行結果如下:
透過@Nested註解,可以讓我們的測試用例更有結構,從邏輯上我們可以將測試用例進行分組,更有可讀性。
重複測試
Junit5的jupiter中提供了重複測試指定次數的能力。 使用@repeatedtest註解的方法來完成。 也可透過@DisplayName自定義重複測試的名稱。
package test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
/**
* @author 小黑說Java
* @ClassName RepeatedTest
* @Description
* @date 2022/1/6
**/
public class RepeateTest {
@RepeatedTest(5)
public void RepeatedTest() {
assertTrue(0 < 5);
}
@RepeatedTest(name = "{displayName} - {currentRepetition}/{totalRepetitions}", value = 5)
@DisplayName("Repeated test")
public void repeatedTestWithDisplayName() {
assertTrue(0 < 5);
}
}
複製程式碼
執行結果如下:
總結
Junit是Java中非常流行的測試框架,在我們日常開發中編寫單元測試會經常用到,新版本的Junit5包含Platform、Jupiter、Vintage三個模組。
在Junit5中新出現了很多創新,支援Java8+的新功能,以及一些不同的測試風格,讓我們在單元測試時能更靈活,擁抱變化,持續學習。
原文連結:https://juejin.cn/post/7050111986360811557