一、動態測試是什么?
動態測試(Dynamic Test)允許在運行時生成測試用例,而不是在編譯時通過 @Test
靜態定義。它通過 @TestFactory
注解標記的方法動態生成一組測試用例,適用于需要靈活生成測試場景的場景。
核心特點:
- 運行時生成測試:測試用例在運行時動態創建。
- 靈活性強:可根據外部數據、條件或復雜邏輯生成測試。
- 獨立執行:每個動態測試作為獨立用例運行,失敗不影響其他用例。
二、動態測試 vs 靜態測試
特性 | 靜態測試(@Test ) | 動態測試(@TestFactory ) |
---|---|---|
定義時機 | 編譯時固定 | 運行時動態生成 |
用例數量 | 固定 | 可動態變化 |
生命周期 | 支持 @BeforeEach /@AfterEach | 不觸發 @BeforeEach /@AfterEach |
適用場景 | 簡單、固定的測試邏輯 | 復雜數據驅動、條件組合測試 |
三、動態測試的核心組件
-
@TestFactory
標記一個方法為動態測試工廠,該方法返回DynamicTest
的集合(如Stream
、Collection
或Iterable
)。 -
DynamicTest
表示單個動態測試用例,包含:- 名稱:測試的顯示名稱。
- 可執行體:測試邏輯(Lambda 或方法引用)。
四、使用場景
1. 數據驅動測試
從外部數據源(如 CSV、數據庫)讀取數據,動態生成測試用例。
2. 組合測試
生成多個參數組合的測試用例,覆蓋不同輸入組合。
3. 條件性測試
根據運行時環境或條件動態決定是否生成測試用例。
4. 動態錯誤處理
例如遍歷一組操作,每個操作作為獨立測試用例,即使部分失敗也不影響其他用例。
五、使用示例
示例 1:基本動態測試
import org.junit.jupiter.api.*;import java.util.stream.Stream;class DynamicTestDemo {@TestFactoryStream<DynamicTest> dynamicTestsBasic() {return Stream.of(DynamicTest.dynamicTest("動態測試 1", () -> {Assertions.assertEquals(4, 2 + 2);}),DynamicTest.dynamicTest("動態測試 2", () -> {Assertions.assertTrue("Hello".startsWith("H"));}));}
}
示例 2:基于外部數據的動態測試(CSV 文件)
假設 test-data.csv
內容:
2,3,5
5,5,10
動態測試代碼:
import org.junit.jupiter.api.*;
import java.nio.file.*;
import java.util.stream.*;class CsvDynamicTest {@TestFactoryStream<DynamicTest> dynamicTestsFromCsv() throws Exception {return Files.lines(Paths.get("src/test/resources/test-data.csv")).map(line -> line.split(",")).map(columns -> DynamicTest.dynamicTest("測試加法: " + columns[0] + "+" + columns[1],() -> {int a = Integer.parseInt(columns[0]);int b = Integer.parseInt(columns[1]);int expected = Integer.parseInt(columns[2]);Assertions.assertEquals(expected, a + b);}));}
}
示例 3:組合參數測試
生成多個參數的組合測試:
@TestFactory
Stream<DynamicTest> dynamicTestsWithParameters() {List<Integer> aList = List.of(1, 2, 3);List<Integer> bList = List.of(4, 5, 6);return aList.stream().flatMap(a -> bList.stream().map(b -> new int[]{a, b})).map(pair -> DynamicTest.dynamicTest("測試 " + pair[0] + " + " + pair[1],() -> Assertions.assertEquals(pair[0] + pair[1], pair[0] + pair[1])));
}
示例 4:條件性動態測試
根據條件決定是否生成測試:
@TestFactory
Stream<DynamicTest> conditionalDynamicTests() {boolean isProduction = checkEnvironment();Stream<DynamicTest> baseTests = Stream.of(DynamicTest.dynamicTest("基礎測試", () -> Assertions.assertTrue(true)));if (isProduction) {Stream<DynamicTest> prodTests = Stream.of(DynamicTest.dynamicTest("生產環境測試", () -> Assertions.assertFalse(false)));return Stream.concat(baseTests, prodTests);}return baseTests;
}private boolean checkEnvironment() {return "prod".equals(System.getProperty("env"));
}
六、動態測試的注意事項
-
生命周期方法不觸發
動態測試不會執行@BeforeEach
或@AfterEach
,需手動管理資源。 -
返回值類型
@TestFactory
方法必須返回Stream
、Collection
或Iterable
類型的DynamicTest
。 -
命名清晰
動態測試的名稱應明確描述測試內容,便于失敗時快速定位。
七、動態測試 vs 參數化測試
特性 | 動態測試 | 參數化測試(@ParameterizedTest ) |
---|---|---|
靈活性 | 高(可自由生成用例) | 中(依賴預定義的參數源) |
生命周期 | 無 @BeforeEach /@AfterEach | 支持生命周期方法 |
適用場景 | 復雜邏輯生成用例 | 固定參數組合測試 |
八、總結
動態測試在以下場景中尤為強大:
- 需要從外部數據源動態生成測試。
- 測試用例數量或參數組合在編譯時未知。
- 需要根據條件動態決定測試邏輯。
通過結合 @TestFactory
和 DynamicTest
,可以極大提升測試的靈活性和覆蓋率。建議在復雜數據驅動或條件組合測試時優先選擇動態測試!