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

参考

相关推荐
武子康2 分钟前
大数据-101 Spark Streaming 有状态转换详解:窗口操作与状态跟踪实战 附多案例代码
大数据·后端·spark
数据小馒头2 分钟前
企业级数据库管理实战(五):多数据库与异构环境的统一管理
后端
天天摸鱼的java工程师4 分钟前
SpringBoot + RabbitMQ + MySQL + XXL-Job:物流系统运单状态定时同步与异常订单重试
后端
RoyLin5 分钟前
TypeScript设计模式:仲裁者模式
前端·后端·typescript
粘豆煮包6 分钟前
掀起你的盖头来之《数据库揭秘》-3-SQL 核心技能速成笔记-查询、过滤、排序、分组等
后端·mysql
召摇20 分钟前
如何避免写垃圾代码:Java篇
java·后端·代码规范
无限大624 分钟前
HTTP 1.0去哪了?揭开Web协议版本误解的真相
后端·面试
程序员蜗牛24 分钟前
![图片](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/62105da0f2c54b3497b0
后端
他日若遂凌云志25 分钟前
深入拆解 Linux Socket 五大 I/O 模型:从底层机制到性能适配
后端
expect7g28 分钟前
COW、MOR、MOW
大数据·数据库·后端