一、动态测试是什么?
动态测试(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
,可以极大提升测试的灵活性和覆盖率。建议在复杂数据驱动或条件组合测试时优先选择动态测试!