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的特性。

参考

相关推荐
凡人的AI工具箱3 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
java亮小白199711 分钟前
Spring循环依赖如何解决的?
java·后端·spring
2301_8112743127 分钟前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
草莓base1 小时前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
Dnelic-1 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Ljw...1 小时前
表的增删改查(MySQL)
数据库·后端·mysql·表的增删查改
编程重生之路1 小时前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端
薯条不要番茄酱1 小时前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
世间万物皆对象8 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
qq_17448285759 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序