? ? ?單元測試是軟件開發過程中的一種驗證手段,它針對最小的可測試部分(通常是函數或方法)進行檢查和驗證。其實單元測試還是挺重要的,不過國內很多公司的項目其實并沒有做好單元測試,或者根本就沒做單元測試,原因可能是項目周期比較緊張,開發時間不充足,所以就省略了單元測試,也有可能是領導不重視單元測試。之前工作中做單元測試主要用到JUnit和TestNG,做覆蓋統計主要用的JaCoCo。不過本篇主要總結JUnit5的知識點及用法。JUnit5官網:JUnit 5
目錄
1.Junit5簡介和環境搭建
2.JUnit 5 的主要注解
?3.JUnit 5 斷言(Assertions)
?4.JUnit 5 測試方法
5.?JUnit 5 測試執行控制
6.JUnit 5 測試輸出
?7.JUnit 5 測試輔助功能
8.JUnit 5 錯誤處理和異常測試
9.JUnit 5 測試依賴注入
10.JUnit 5 測試監聽器
11.JUnit 5 測試配置
12.?JUnit 5 測試動態生成
13.JUnit 5 測試參數化
14.JUnit 5 測試并行執行
15.JUnit 5 測試可讀性
16.JUnit 5 測試條件
17.Spring Boot項目集成Junit5
? ? ?1.Junit5簡介和環境搭建
特性 | 描述 |
---|---|
JUnit 5 架構 | JUnit 5 由三個主要模塊組成:JUnit Platform、JUnit Jupiter 和 JUnit Vintage。 |
JUnit Platform | 提供了一個測試框架的運行時平臺,允許IDE和構建工具在JVM上啟動和請求測試。 |
JUnit Jupiter | 提供了新的編程模型和擴展模型,用于編寫測試。 |
JUnit Vintage | 允許JUnit 5運行JUnit 3和JUnit 4的測試。 |
環境搭建 | 使用Maven或Gradle將JUnit 5添加到項目中。 |
環境搭建:
?使用Maven或Gradle將JUnit 5添加到項目中。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId><version>5.7.0</version><scope>test</scope></dependency>?
2.JUnit 5 的主要注解
注解 | 描述 | 代碼示例 |
---|---|---|
@BeforeEach | 在每個測試方法執行之前運行的方法。 | |
@AfterEach | 在每個測試方法執行之后運行的方法。 | @AfterEach public void tearDown() { ... } |
@BeforeAll | 在所有測試方法執行之前,整個類中只運行一次。 | ?@BeforeAll public static void setUpBeforeClass() { ... } |
@AfterAll | 在所有測試方法執行之后,整個類中只運行一次。 | ?@AfterAll public static void tearDownAfterClass() { ... } |
@Test | 標記一個方法為測試方法。 | ?@Test public void myTestMethod() { ... } |
@RepeatedTest | 允許測試方法重復執行指定次數。 | ?@RepeatedTest(10) public void repeatedTestMethod() { ... } |
@ParameterizedTest | 用于參數化測試。 | ?@ParameterizedTest public void parameterizedTestMethod(int param) { ... } |
@MethodSource | 與@ParameterizedTest 一起使用,提供測試參數。 | |
?3.JUnit 5 斷言(Assertions)
斷言方法 | 描述 | 代碼示例 |
---|---|---|
assertAll() | 允許組合多個斷言,如果任何一個斷言失敗,測試會立即失敗。 | ?assertAll("Test Group", () -> assertEquals(2, 1 + 1), () -> assertEquals("foo", "bar")); |
assertNotNull() | 驗證對象不是null 。 | ?assertNotNull("Object should not be null", myObject); |
assertNull() | 驗證對象是null 。 | ?assertNull("Object should be null", myObject); |
assertTrue() | 驗證條件為true 。 | ?assertTrue("Should be true", condition); |
assertFalse() | 驗證條件為false 。 | ?assertFalse("Should be false", condition); |
assertEquals() | 驗證兩個值是否相等。 | ?assertEquals(2, 1 + 1); |
assertNotEquals() | 驗證兩個值是否不相等。 | ?assertNotEquals("Should not be equal", 2, 3); |
assertSame() | 驗證兩個引用是否指向同一個對象。 | ?Object obj1 = new Object(); Object obj2 = obj1; assertSame(obj1, obj2); |
assertNotSame() | 驗證兩個引用是否指向不同的對象。 | ?assertNotSame("Should not be same", obj1, obj2); |
以上是JUnit 5中常用的斷言方法,它們幫助開發者驗證測試用例中的預期結果是否符合實際結果。
?4.JUnit 5 測試方法
特性 | 描述 | 代碼示例 |
---|---|---|
測試方法 | 使用@Test 注解標記的方法,JUnit 5將自動運行這些方法作為測試。 | ?@Test public void testMethod() { ... } |
測試方法參數 | 測試方法可以接收參數,如測試數據。 | ?@ParameterizedTest public void testMethod(String data) { ... } |
超時測試 | 使用@Timeout 注解設置測試方法的最大執行時間。 | ?@Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) public void testMethod() { ... } |
動態測試 | 使用@DynamicTest 注解創建動態生成的測試。 | ? |
條件測試 | 使用@EnabledIf 或@DisabledIf 注解根據條件啟用或禁用測試。 | ?@EnabledIf("expression") @Test public void testMethod() { ... } |
5.?JUnit 5 測試執行控制
控制方式 | 描述 | 代碼示例 |
---|---|---|
標簽(Tags) | 使用@Tag 注解給測試分類,可以通過標簽過濾運行特定測試。 | ?@Tag("fast") @Test public void fastTestMethod() { ... } |
測試配置(Test Configuration) | 使用@TestInstance 注解控制測試方法的生命周期。 | ?@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyTestClass { ... } |
測試篩選器(Test Filters) | 使用JUnit 5的內置篩選器來選擇要運行的測試。 | 在命令行中使用?-@include ?或?--exclude ?選項。 |
測試依賴(Test Dependencies) | 使用@ExtendWith 注解定義測試類或方法的依賴關系。 | ?@ExtendWith(CustomExtension.class) public class MyTestClass { ... } |
重復測試(Repeated Tests) | 使用@RepeatedTest 注解讓測試方法重復執行。 | ?@RepeatedTest(5) public void repeatedTestMethod() { ... } |
參數化測試(Parameterized Tests) | 使用@ParameterizedTest 和@MethodSource 注解執行參數化測試。 | ?@ParameterizedTest @MethodSource("dataProvider") public void parameterizedTest(int param) { ... } static Stream<?> dataProvider() { return Stream.of(1, 2, 3); } |
臨時文件夾(Temporary Folders) | 使用@TempDir 注解為測試方法提供臨時文件夾路徑。 | ?@Test public void testWithTempFolder(@TempDir Path tempDir) { ... } |
以上是JUnit 5中測試執行控制的一些關鍵特性,它們允許開發者更靈活地控制測試的執行流程和條件。
6.JUnit 5 測試輸出
特性 | 描述 | 代碼示例 |
---|---|---|
斷言消息 | 使用斷言方法的重載版本,提供自定義的失敗消息。 | ?assertEquals("List should contain 'B'", "A", list.get(1)); |
日志記錄 | 使用@LogMessageRule 注解捕獲日志消息。 | ?@Rule public LogMessageRule rule = new LogMessageRule(); @Test public void testLogCapture() { rule.expect(WARNING); logger.warn("This is a warning message"); } |
測試輸出(Test Output) | 使用@TestInfo 獲取測試信息,如測試方法名稱、顯示名稱等。 | ?@Test public void testWithTestInfo(TestInfo testInfo) { System.out.println("Running test: " + testInfo.getDisplayName()); } |
測試模板方法(Test Template Methods) | 使用@TestTemplate 注解定義模板方法,結合@ExtendWith 注解使用。 | ?@TestTemplate public void testTemplateMethod(MyCustomExtension ext) { ... } @ExtendWith(MyCustomExtension.class) public void extendWithMethod() { ... } |
動態測試(Dynamic Tests) | 生成動態測試用例,返回Stream 的DynamicTest 。 | ?@TestFactory Stream<DynamicTest> dynamicTests() { return Stream.of("A", "B", "C") .map(input -> DynamicTest.dynamicTest(input, () -> assertEquals(1, input.length()))); } |
?7.JUnit 5 測試輔助功能
功能 | 描述 | 代碼示例 |
---|---|---|
假設(Assumptions) | 使用假設來避免在不滿足特定條件時執行測試。 | assumeTrue("This test assumes JDK 11 or higher", javaVersion >= 11); |
測試時鐘(Test Clock) | 使用@MockClock 注解模擬時間,用于時間相關的測試。 | ?@Test @MockClock("12:00:00") public void testWithMockClock() { ... } |
測試資源(Test Resources) | 使用@RegisterExtension 注解注冊測試資源,如臨時文件、數據庫連接等。 | ?@RegisterExtension public TemporaryFolder folder = new TemporaryFolder(); @Test public void testWithTemporaryFolder() { Path path = folder.getRoot().toPath(); ... } |
測試規則(Test Rules) | 使用測試規則來為測試方法提供額外的行為,如日志捕獲、重復測試等。 | ?@Rule public TestRule logWatcher = new LogWatcher(); @Test public void testWithLogWatcher() { ... } |
條件測試(Conditional Tests) | 根據系統屬性或環境變量的條件執行測試。 | ?@EnabledIfEnvironment("os.name == 'Windows 10'") @Test public void windows10OnlyTest() { ... } |
測試配置參數(Test Configuration Parameters) | 從命令行或配置文件中讀取參數,并在測試中使用。 | ?@Test public void testWithConfigurationParameter(@ConfiguredParameter("timeout") int timeout) { ... } |
測試模板方法(Test Template Methods) | 允許為測試提供自定義的執行邏輯。 | ?@TestTemplate public void testTemplateMethod(MyCustomExtension ext) { ... } @ExtendWith(MyCustomExtension.class) public void extendWithMethod() { ... } |
這些輔助功能增強了JUnit 5的測試能力,使得測試更加靈活和強大
8.JUnit 5 錯誤處理和異常測試
特性 | 描述 | 代碼示例 |
---|---|---|
期望異常(Expected Exceptions) | 使用assertThrows 來驗證方法是否拋出了特定的異常。 | ?Assertions.assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("bad argument"); }); |
異常測試(Exception Testing) | 使用@Test 注解的expectedExceptions 屬性來測試預期的異常。 | ?@Test(expectedExceptions = ArithmeticException.class) public void testDivideByZero() { int i = 1 / 0; } |
斷言異常內容(Asserting Exception Content) | 捕獲異常并驗證其內容,如消息或原因。 | ?Exception exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("error"); }); assertEquals("error", exception.getMessage()); |
錯誤收集(Error Collecting) | 使用assertAll 來執行多個斷言,即使其中一個失敗,其他斷言也會繼續執行。 | ?assertAll("test", () -> assertEquals(2, 1 + 1), () -> assertThrows(RuntimeException.class, () -> { throw new RuntimeException(); })); |
軟斷言(Soft Assertions) | 使用軟斷言來收集多個失敗的斷言,而不是在第一個失敗時立即停止測試。 | ?SoftAssertions softly = new SoftAssertions(); softly.assertThat(codePointBefore('a')).isEqualTo(-1); softly.assertThat(codePointBefore('A')).isEqualTo(-1); softly.assertAll(); |
?這些特性幫助開發者更好地處理測試中的異常情況,確保測試的準確性和健壯性。
9.JUnit 5 測試依賴注入
特性 | 描述 | 代碼示例 |
---|---|---|
構造器注入 | 使用@Autowired 注解在測試類構造器中注入依賴。 | ?@SpringBootTest public class MySpringBootTest { @Autowired private MyService service; } |
字段注入 | 使用@Inject 注解在字段上注入依賴。 | ?public class MyTestClass { @Inject private MyService service; } |
方法參數注入 | 使用@InjectMocks 注解在測試方法的參數上注入依賴。 | @Test public void testMethod(@Mocked MyDependency dependency) { ... } |
模塊化測試 | 使用@ExtendWith 注解和自定義擴展來模塊化測試邏輯。 | ?@ExtendWith(MyExtension.class) public class MyTestClass { ... } |
測試實例化 | 使用@TestInstance 注解控制測試類的實例化方式。 | ?@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyTestClass { ... } |
測試上下文管理 | 使用@TestInfo 獲取測試上下文信息,如測試方法名稱、測試類等。 | public class MyTestClass { @Test public void testMethod(TestInfo testInfo) { System.out.println(testInfo.getDisplayName()); } } |
依賴注入是現代測試框架中的重要特性,它允許測試代碼更加模塊化和可重用。JUnit 5通過集成Spring等框架,提供了強大的依賴注入支持。
10.JUnit 5 測試監聽器
特性 | 描述 | 代碼示例 |
---|---|---|
監聽器(Listeners) | 使用@ExtendWith 注解添加監聽器,監聽測試的生命周期事件。 | ?@ExtendWith(MyTestWatcher.class) public class MyTestClass { ... } |
測試執行監聽器(Test Execution Listeners) | 實現TestExecutionListener 接口,監聽測試的執行過程。 | ?public class MyTestExecutionListener implements TestExecutionListener { ... } |
測試實例監聽器(Test Instance Listeners) | 實現TestInstanceListener 接口,監聽測試實例的創建和生命周期。 | public class MyTestInstanceListener implements TestInstanceListener { ... } |
測試生命周期監聽器(Test Lifecycle Listeners) | 實現TestLifecycleListener 接口,監聽測試的整個生命周期。 | ?public class MyTestLifecycleListener implements TestLifecycleListener { ... } |
測試失敗監聽器(Test Failure Listeners) | 實現TestFailureListener 接口,監聽測試失敗事件。 | ?public class MyTestFailureListener implements TestFailureListener { ... } |
測試告警監聽器(Test Alerting Listeners) | 實現TestAlerting 接口,對測試失敗進行告警。 | ?public class MyTestAlerting implements TestAlerting { ... } |
測試監聽器是JUnit 5中用于監聽和響應測試事件的強大機制,它們可以用來擴展JUnit 5的功能,如測試報告生成、性能監控等。
11.JUnit 5 測試配置
配置項 | 描述 | 使用示例 |
---|---|---|
全局配置 | 通過junit-platform.properties 文件進行全局配置。 | junit.jupiter.conditions.include-classes-with-at-least-one-method = true |
測試方法配置 | 使用注解在測試方法上指定配置。 | ?@Test @DisplayName("A test with custom display name") void testMethod() { ... } |
測試類配置 | 使用注解在測試類上指定配置。 | ?@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyTestClass { ... } |
測試模板配置 | 使用注解在測試模板方法上指定配置。 | ?@TestTemplate public void testTemplate() { ... } |
參數化測試配置 | 使用注解在參數化測試方法上指定配置。 | ?@ParameterizedTest(name = "{index} => {0} + {1} = {2}") @MethodSource("dataProvider") void parameterizedTest(int a, int b, int expected) { ... } |
測試過濾器配置 | 使用注解或命令行參數來過濾測試。 | 在命令行中使用?--filter ?選項。 |
測試重復配置 | 使用注解在測試方法上指定重復次數。 | ?@RepeatedTest(3) void repeatedTestMethod() { ... } |
測試超時配置 | 使用注解在測試方法上指定執行超時時間。 | @Test @Timeout(duration = 500, unit = TimeUnit.MILLISECONDS) void testMethod() { ... } |
測試配置是JUnit 5中用于定制測試行為的重要特性,它允許開發者根據需要調整測試的執行方式。
12.?JUnit 5 測試動態生成
特性 | 描述 | 代碼示例 |
---|---|---|
動態測試(Dynamic Tests) | 允許在測試執行期間動態生成測試用例。 | @TestFactory Stream<DynamicTest> dynamicTests() { return Stream.of("foo", "bar") .map(s -> DynamicTest.dynamicTest(s, () -> assertNotEquals(0, s.length()))); } |
測試工廠(Test Factories) | 創建動態測試的方法,可以返回Stream 或Iterable 的DynamicTest 。 | ?@TestFactory Stream<DynamicTest> dynamicTestsWithStream() { return Stream.of(1, 2, 3) .map(i -> DynamicTest.dynamicTest("Test with " + i, () -> {})); } |
測試模板方法(Test Template Methods) | 使用模板方法來定義測試邏輯,并通過擴展執行不同的測試用例。 | ?@TestTemplate void testWithCustomProvider(Object o) { ... } @ExtendWith(CustomProviderExtension.class) void extendWithCustomProvider() { ... } |
測試擴展(Test Extensions) | 自定義擴展可以介入測試執行的各個階段,實現自定義邏輯。 | ?public class CustomExtension implements TestExecutionListener { ... } |
動態生成測試用例是JUnit 5中一個強大的特性,它允許開發者根據需要靈活地生成測試用例,從而提高測試的復用性和靈活性。
13.JUnit 5 測試參數化
特性 | 描述 | 代碼示例 |
---|---|---|
參數化測試(Parameterized Tests) | 允許為單個測試方法提供多個輸入參數。 | ?@ParameterizedTest @MethodSource("numbersProvider") void parameterizedTest(int number) { ... } static Stream<Integer> numbersProvider() { return Stream.of(1, 2, 3); } |
方法源(Method Source) | 提供測試參數的來源,可以是靜態方法或字段。 | ?static Stream<Arguments> numbersProvider() { return Stream.of(Arguments.of(1), Arguments.of(2), Arguments.of(3)); } |
CSV源(CSV Source) | 使用CSV格式的字符串直接提供測試參數。 | ?@ParameterizedTest @CsvSource({ "1, 2, 3", "4, 5, 6" }) void parameterizedTest(int a, int b, int c) { ... } |
對象數組源(Object Array Source) | 使用對象數組直接提供測試參數。 | ?@ParameterizedTest @ArgumentsSource(ObjectArraySource.class) void parameterizedTest(String data) { ... } |
自定義提供器(Custom Providers) | 創建自定義的參數提供器來生成測試參數。 | ?public class CustomProvider implements ArgumentsProvider { public Stream<? extends Arguments> provideArguments(ExtensionContext context) { return Stream.of(Arguments.of("A", "B")); } } |
參數化測試是JUnit 5中用于測試多種輸入組合的強大特性,它允許開發者編寫更簡潔、更高效的測試代碼。
14.JUnit 5 測試并行執行
特性 | 描述 | 代碼示例 |
---|---|---|
并行執行(Parallel Execution) | 允許同時運行多個測試,以加快測試套件的執行速度。 | 使用JUnit 5的junit.jupiter.execution.parallel.enabled 配置屬性來啟用并行執行。 |
測試類級別的并行 | 對整個測試類中的測試方法進行并行執行。 | ?@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyTestClass { ... } |
方法級別的并行 | 對單個測試方法進行并行執行。 | 使用JUnit 5的junit.jupiter.execution.parallel.mode.default 配置屬性來設置默認的并行模式。 |
自定義并行策略 | 通過實現TestExecutionListener 接口來自定義測試的并行執行策略。 | ?public class CustomParallelismListener implements TestExecutionListener { ... } |
資源隔離 | 確保在并行執行過程中,測試之間的資源是隔離的,避免相互干擾。 | 使用@RegisterExtension 注解的ResourceLock 規則來鎖定特定資源。 |
動態測試并行 | 對動態生成的測試用例進行并行執行。 | 在@TestFactory 方法中返回的DynamicTest 流可以被并行執行。 |
測試并行執行是提高測試效率的重要特性,它可以有效減少持續集成(CI)環境中的測試等待時間。
15.JUnit 5 測試可讀性
特性 | 描述 | 代碼示例 |
---|---|---|
顯示名稱(Display Name) | 為測試方法提供可讀性強的顯示名稱。 | ?@DisplayName("Test with custom display name") @Test public void testMethod() { ... } |
嵌套測試(Nested Tests) | 使用嵌套的測試方法來組織測試邏輯,提高測試的可讀性。 | ?@Test public void outerTest() { @Test public void innerTest() { ... } } |
測試描述(Test Description) | 提供測試的描述信息,增強測試的可讀性。 | 使用TestInfo 獲取測試的描述信息,并在測試日志中展示。 |
斷言消息(Assertion Messages) | 在斷言失敗時提供自定義的消息,幫助理解失敗的原因。 | ?assertEquals("Expected reference equality", obj1, obj2); |
測試模板(Test Templates) | 使用測試模板方法來提供可讀性強的測試邏輯。 | ?@TestTemplate void testWithCustomProvider(Object o) { ... } |
測試可讀性是JUnit 5中用于提高測試代碼和測試報告可讀性的重要特性,它有助于開發者更好地理解和維護測試代碼。
16.JUnit 5 測試條件
特性 | 描述 | 代碼示例 |
---|---|---|
條件注解(Conditional Annotations) | 根據條件啟用或禁用測試,如系統屬性、環境變量等。 | ?@EnabledIf("javaVersion > 11") @Test public void testMethod() { ... } |
系統屬性條件(System Property Condition) | 根據系統屬性的值來啟用或禁用測試。 | ?@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") @Test public void testMethod() { ... } |
環境變量條件(Environment Variable Condition) | 根據環境變量的值來啟用或禁用測試。 | ?@EnabledIfEnvironmentVariable(named = "CI", matches = "true") @Test public void testMethod() { ... } |
自定義條件(Custom Conditions) | 實現Condition 接口來提供自定義的條件邏輯。 | ?public class CustomCondition implements Condition { ... } |
測試配置參數(Test Configuration Parameters) | 從命令行或配置文件中讀取參數,并在測試中使用。 | ?@Test public void testWithConfigurationParameter(@ConfiguredParameter("timeout") int timeout) { ... } |
測試條件是JUnit 5中用于根據環境或配置來控制測試執行的重要特性,它允許開發者靈活地決定哪些測試應該運行。
17.Spring Boot項目集成Junit5
? 17.1首先,確保你的pom.xml
(Maven)或build.gradle
(Gradle)文件中包含了JUnit 5和Spring Boot Test的依賴。
對于Maven,pom.xml
可能包含以下依賴:
<dependencies><!-- Spring Boot Test Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.5.0</version> <!-- 使用你項目匹配的版本 --><scope>test</scope></dependency>
</dependencies>
?對于Gradle,build.gradle
可能包含以下依賴:
dependencies {// Spring Boot Test StartertestImplementation('org.springframework.boot:spring-boot-starter-test:2.5.0') // 使用你項目匹配的版本
}
17.2 創建測試類
創建一個測試類,使用@SpringBootTest
注解來指示Spring Boot為測試提供支持。使用@Test
注解標記測試方法。
這里是一個簡單的服務層測試示例:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
public class SomeServiceTest {@Autowiredprivate SomeService someService;@MockBeanprivate SomeDependency someDependency;@Testpublic void testSomeMethod() {// 設置mock行為when(someDependency.someMethod()).thenReturn("expected value");// 調用待測試的方法String result = someService.someMethod();// 驗證結果assertEquals("expected value", result);// 驗證依賴是否被調用verify(someDependency).someMethod();}
}
?這個例子中,SomeService
是我們想要測試的服務層組件,而SomeDependency
是它的一個依賴項,我們使用@MockBean
注解來創建一個模擬對象。
17.3 運行測試
你可以在IDE中運行測試,或者使用Maven或Gradle的命令行工具來執行測試。
對于Maven,使用以下命令:
mvn test
?對于Gradle,使用以下命令:
./gradlew test
?這只是一個極簡單的示例,在項目的具體需求下,測試會更復雜,包括更多的模擬對象,服務和測試用例等。