# Spring单元测试
# Junit
# 简介
JUnit 是一种广泛使用的单元测试框架,用于在 Java 中编写和运行测试。JUnit 作为测试驱动开发(TDD)的重要工具,能够帮助开发者编写可重复执行的测试,确保代码的正确性和质量。它提供了注解、断言和运行器等功能,支持自动化测试的执行和结果验证。
# 版本
JUnit 3.x:早期版本,基于继承的方式组织测试类和方法。
JUnit 4.x:引入了注解机制,简化了测试的编写方式,并且可以直接继承 TestCase
类。
JUnit 5.x:是当前的版本,具有更强大的功能和更高的灵活性,支持模块化架构。JUnit 5 的核心由以下三部分组成:
- JUnit Platform:提供了运行测试的基础设施。
- JUnit Jupiter:JUnit 5 的编程模型,提供新的注解和断言方法。
- JUnit Vintage:用于支持运行 JUnit 3 和 JUnit 4 的测试。
# 测试方法命名
- 清晰、简洁:方法名应清晰地描述测试的目标、条件和预期结果。
- 遵循一致性:保持命名风格的一致性,便于团队成员理解和维护。
- 行为驱动开发(BDD)风格:可以考虑使用
given-when-then
风格命名,尤其是当测试涉及复杂的场景时。
# 注解
注解 | 描述 |
---|---|
@Test | 表示该方法是测试方法 |
@ParameterizedTest | 参数化测试,需要配合参数源注解使用(如 @ValueSource 、@CsvSource ) |
@RepeatedTest | 重复测试、验证一致性、模拟压力测试 |
@TestFactory | 允许运行时根据逻辑生成一组动态的测试用例,批量测试 |
@TestTemplate | 定义测试模板,需要实现TestTemplateInvocationContextProvider 类 |
@TestClassOrder | 测试类执行顺序,有内置排序器,也可与实现了ClassOrderer 接口的类一起使用 |
@TestMethodOrder | 测试方法执行顺序,需要与实现了MethodOrderer 接口的类一起使用 |
@TestInstance | 测试类的生命周期,每个方法一个测试实例,或每个类一个实例 |
@DisplayName | 自定义显示名称,使得测试更容易理解 |
@DisplayNameGeneration | 为测试类中的所有测试方法自动生成显示名称 |
@BeforeEach | 在每个测试方法执行之前运行的方法,初始化测试资源 |
@AfterEach | 在每个测试方法执行之后运行的方法 |
@BeforeAll | 在整个测试类中的所有测试方法执行之前运行的方法,初始化一次性资源 |
@AfterAll | 在整个测试类中的所有测试方法执行之后运行的方法 |
@Nested | 嵌套测试类 |
@Disabled | 禁用测试方法或测试类,调试的时候使用 |
@Tag | 声明用于过滤测试的标签 |
@AutoClose | 自动关闭的资源 |
@Timeout | 定义超时限制 |
@TempDir | 自动创建临时目录 |
@ExtendWith | 用于扩展测试 |
@RegisterExtension | 实例化扩展 |
# 组合注释
正常使用标签注解
@Test
@Tag("fast")
void test(){
System.out.println("测试方法");
}
自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {
}
简洁的使用
@FastTest
void test(){
System.out.println("测试方法");
}
# 断言Assert
JUnit 5 的断言类位于 org.junit.jupiter.api.Assertions
包中,包含了许多有用的静态方法
# 断言方法
断言方法 | 作用 |
---|---|
assertEquals | 验证两个值是否相等 |
assertNotEquals | 验证两个值是否不相等 |
assertTrue | 验证条件是否为 true |
assertFalse | 验证条件是否为 false |
assertNull | 验证对象是否为 null |
assertNotNull | 验证对象是否不为 null |
assertArrayEquals | 验证两个数组是否相等 |
assertThrows | 验证是否抛出了特定类型的异常 |
assertTimeout | 验证操作是否在规定时间内完成 |
assertAll | 组合多个断言一起执行 |
# 第三方断言库
例如AssertJ (opens new window)、Hamcrest (opens new window)、Truth (opens new window)等
# 假设Assume
当继续执行给定测试没有意义时,可以使用假设,例如,测试依赖于当前运行环境不存在的东西。
- 假设有效时,假设方法不会引发异常,测试继续执行
- 假设无效是,会抛出类型异常org.opentest4j.TestAbortedException
所有 JUnit Jupiter 假设都是org.junit.jupiter.api.Assumptions
类中的静态方法
# 假设方法
假设方法 | 作用 |
---|---|
assumeTrue | 如果条件为True,继续执行,否则跳过测试 |
assumeFalse | 如果条件为False,继续执行,否则跳过测试 |
assumeNotNull | 如果对象不为null,继续执行,否则跳过测试 |
assumeNull | 如果对象为null,继续执行,否则跳过测试 |
# 和断言的比较
假设:假设用于设定前提条件,确保测试在某些条件下才会执行如果条件不满足,测试会被跳过,而不是失败
断言:断言用于验证实际结果与预警结果是否一致,如果不一致,测试会失败
# 测试执行顺序
# 方法顺序
内置MethodOrderer
实现:
MethodOrderer.DisplayName
:根据显示名称按字母顺序对测试方法进行排序MethodOrderer.MethodName
:根据测试方法的名称和形式参数列表按字母顺序对其进行排序MethodOrderer.OrderAnnotation
:根据通过注释指定的值按数字方式对测试方法进行排序@Order
MethodOrderer.Random
:以伪随机方式排列测试方法,并支持配置自定义种子MethodOrderer.Alphanumeric
:根据测试方法的名称和形式参数列表*按字母顺序对其进行排序已弃用,改为使用
MethodOrderer.MethodName
,将在 6.0 版中删除
# 类顺序
内置ClassOrderer
实现:
ClassOrderer.ClassName
:根据测试类的完全限定类名按字母顺序对其进行排序ClassOrderer.DisplayName
:根据显示名称的字母数字顺序对测试类进行排序(请参阅显示名称生成优先规则 (opens new window))ClassOrderer.OrderAnnotation
:根据通过注释指定的值按数字方式对测试类进行排序@Order
ClassOrderer.Random
:伪随机排序测试类并支持配置自定义种子
# 异常处理(补充)
# 参数化测试
参数化测试允许使用不同的参数多次运行测试,此外必须至少声明一个数据源
# 数据源
# @ValueSource
允许指定一个数组,每次为参数化测试调用提供一个参数
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
assertTrue(argument > 0 && argument < 4);
}
无效数据源和空源:
# @EnumSource
用于枚举类型的数据源,提供枚举类型的所有常量
enum Season { WINTER, SPRING, SUMMER, FALL }
@ParameterizedTest
@EnumSource(Season.class) // 使用所有枚举常量
void testWithEnumSource(Season season) {
assertNotNull(season);
}
# @MethodSource
指定一个返回流或者数组的静态方法,这个方法可以返回 Stream
、Collection
、Iterable
、Iterator
或数组
@ParameterizedTest
@MethodSource("stringProvider")
void testWithMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana", "cherry");
}
# @FieldSource
引用测试类或外部类的一个或多个字段
@ParameterizedTest
@FieldSource("listOfFruits")
void singleFieldSource(String fruit) {
assertFruit(fruit);
}
static final List<String> listOfFruits = Arrays.asList("apple", "banana");
# @CsvSource
用于提供csv格式的多个数据行,每一行数据对应测试方法的一个参数集,多个参数使用逗号分隔
@ParameterizedTest
@CsvSource({
"apple, 1",
"banana, 2",
"cherry, 3"
})
void testWithCsvSource(String fruit, int rank) {
assertNotNull(fruit);
assertTrue(rank > 0);
}
# @CsvFileSource
和@CsvSource类似,允许从CSV文件加载数据
@ParameterizedTest
@CsvFileSource(resources = "/testData.csv", numLinesToSkip = 1)
void testWithCsvFileSource(String name, int age) {
assertNotNull(name);
assertTrue(age > 0);
}
# @ArgumentsSource
用于指定一个实现了 ArgumentsProvider
接口的类或方法
class CustomArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(Arguments.of("apple", 1), Arguments.of("banana", 2));
}
}
@ParameterizedTest
@ArgumentsSource(CustomArgumentsProvider.class)
void testWithArgumentsSource(String fruit, int rank) {
assertNotNull(fruit);
assertTrue(rank > 0);
}