junit4用了这么久,来试试junit5中的参数化测试提高单测效率吧

大家好,这里是小奏 ,觉得文章不错可以关注公众号小奏技术

junit5之前的单元测试

为了更好的说明junit5的参数化测试,我们先来看看junit5之前不使用参数测试后的的单元测试是怎么写的。

比如我们现在编写了一个邮箱验证的工具类,

java 复制代码
public class StringValidator {
    
    public static boolean isValidEmail(String email) {
        return email != null && email.matches("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])");
    }
}

不使用参数化测试

我们需要对这个工具类进行单元测试,我们可以这样写:

java 复制代码
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class StringUtilsTest {

    @Test
    void testIsValidEmail() {
        assertTrue(StringValidator.isValidEmail("xiaozou@example.com"));
        assertTrue(StringValidator.isValidEmail("xiao.zou@domain.com"));
        assertTrue(StringValidator.isValidEmail("xiaozou+tag@example.co.uk"));

        assertFalse(StringValidator.isValidEmail("xiaozou@email"));
        assertFalse(StringValidator.isValidEmail("@xiaozou.com"));
        assertFalse(StringValidator.isValidEmail("no at sign"));
        assertFalse(StringValidator.isValidEmail(null));
    }
}

运行结果

可以看到这么多测试用例就只有一个运行结果

使用参数化测试

首先引入junut5的依赖:

xml 复制代码
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>

然后我们使用junit5中的参数化测试功能来编写单元测试

我们可以将上面的测试用例改写成如下形式:

java 复制代码
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.NullSource;
import static org.junit.jupiter.api.Assertions.*;

public class StringValidatorTest {

    @ParameterizedTest
    @ValueSource(strings = {
        "xiaozou@example.com",
        "xiao.zou@domain.com",
        "xiaozou+tag@example.co.uk"
    })
    void testValidEmails(String email) {
        assertTrue(StringValidator.isValidEmail(email));
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "xiaozou@email",
        "@xiaozou.com",
        "no at sign"
    })
    @NullSource
    void testInvalidEmails(String email) {
        assertFalse(StringValidator.isValidEmail(email));
    }

运行结果

可以看到使用参数化测试后,我们仅需要将自己需要测试的参数写入到注解中即可运行多次,而不再需要写多个测试用例

而且测试报告相比之前更加清晰,可以看到每个测试用例的执行结果

参数化测试常用注解说明

ParameterizedTest

@ParameterizedTest注解用于指定一个参数化测试方法,该方法将会被多次调用,每次调用时传入不同的参数。和@Test效果类似,主要是用于标记。

@ValueSource

@ValueSource注解用于指定一个或多个常量值,这些值将会被传入到测试方法中。

@ValueSource支持8中基本的数据类型,以及String类型,Class类型。

如果要使用枚举参数我们可以使用@EnumSource注解

@EnumSource

java 复制代码
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnumSource(TimeUnit timeUnit) {
    assertNotNull(timeUnit);
}

@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = { "DAYS", "HOURS" })
void testWithEnumSourceInclude(TimeUnit timeUnit) {
    assertTrue(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
}

@EnumSource中的names属性用于指定枚举类型中的枚举值,如果不指定,则默认使用所有的枚举值。

更多用法可以参考 doczhcn.gitbook.io/junit5/inde...

@CsvSource

@CsvSource主要用于传入多个参数。

比如我们可以传入输入和预期结果进行校验

java 复制代码
@ParameterizedTest
@CsvSource({"xiaoZou,XIAOZOU", "golang,GOLANG", "java,JAVA"})
void toUpperCase_ShouldGenerateTheExpectedUppercaseValue(String input, String expected) {
    String actualValue = input.toUpperCase();
    assertEquals(expected, actualValue);
}

当然也可以就正常传入多个参数测试

java 复制代码
@ParameterizedTest
@CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" })
void testWithCsvSource(String first, int second) {
    assertNotNull(first);
    assertNotEquals(0, second);
}

@CsvFileSource

@CsvFileSource让你使用classpath中的CSV文件。CSV文件中的每一行都会导致参数化测试的一次调用。

java 复制代码
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv")
void testWithCsvFileSource(String first, int second) {
    assertNotNull(first);
    assertNotEquals(0, second);
}

类似读取excel. 如果有表头,可以使用numLinesToSkip = 1来跳过表头

总结

参数化测试是junit5中的一个非常有用的特性,使用参数化测试(@ParameterizedTest)有如下好处

  1. 代码更简洁:每个测试方法只需编写一次断言逻辑,减少了重复代码
  2. 更易于维护:添加新的测试用例只需在 @ValueSource 中添加新的值,而不需要编写新的测试方法
  3. 更好的可读性:测试用例和测试逻辑分离,使得测试意图更加清晰
  4. 更好的测试报告:每个参数都作为单独的测试用例运行,在测试报告中可以清楚地看到每个用例的结果。

junit5提供了非常多的新特性,我们推荐大家使用junit5来编写单元测试,更多junit5的特性可以参考官方文档。

如果我们是在spring-boot中进行编写单元测试,spring-boot-test中默认是引入了junit5依赖的,所以我们可以直接使用junit5的特性。

参考

相关推荐
金銀銅鐵6 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
念何架构之路7 小时前
go-grpc设计思想
开发语言·后端·golang
system_error_x7 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
步行cgn9 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈10 小时前
AI 读你的提示词,其实很没耐心
后端
用户83562907805111 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我1234511 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒11 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记11 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk5987811 小时前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端