使用 JUnit 5.7 进行参数化测试:深入了解 @EnumSource

这篇具有很好参考价值的文章主要介绍了使用 JUnit 5.7 进行参数化测试:深入了解 @EnumSource。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

使用 JUnit 5.7 进行参数化测试:深入了解 @EnumSource    

     参数化测试允许开发人员使用一系列输入值高效地测试他们的代码。在 JUnit 测试领域,经验丰富的用户长期以来一直在努力解决实施这些测试的复杂问题。但随着 JUnit 5.7 的发布,测试参数化进入了一个新时代,为开发人员提供了一流的支持和增强的功能。让我们深入探讨 JUnit 5.7 为参数化测试带来的激动人心的可能性!

使用 JUnit 5.7 进行参数化测试:深入了解 @EnumSource

JUnit 5.7 文档中的参数化示例 让我们看看文档中的一些示例:

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
     assertTrue(StringUtils.isPalindrome(candidate));
}

@ParameterizedTest
@CsvSource({
     "apple,         1",
     "banana,        2",
     "'lemon, lime', 0xF1",
     "strawberry,    700_000"
})


void testWithCsvSource(String fruit, int rank) {
     assertNotNull(fruit);
     assertNotEquals(0, rank);
}

@ParameterizedTest
@MethodSource("stringIntAndListProvider")
void testWithMultiArgMethodSource(String str, int num, List<String> list) {
     assertEquals(5, str.length());
     assertTrue(num >=1 && num <=2);
     assertEquals(2, list.size());
}

static Stream<Arguments> stringIntAndListProvider() {
     return Stream.of(
         arguments("apple", 1, Arrays.asList("a", "b")),
         arguments("lemon", 2, Arrays.asList("x", "y"))
     );
}

在使用 @ParameterizedTest 注解的同时,还必须使用所提供的几个源注解之一,说明从何处获取参数。参数来源通常被称为 "数据提供者"。

@RunWith(JUnit4.class)
@SpringBootTest
public class MyTest {

    @ParameterizedTest
     @ValueSource(ints = {1, 2, 3})
     public void testWithIntValue(int value) {
         // ...
     }

}

在此,我将不对其进行详细描述:JUnit 用户指南比我做得更好,但请允许我分享几点看法:

@ValueSource 只限于提供单个参数值。换句话说,测试方法不能有一个以上的参数,而且可以使用的类型也受到限制。
@CsvSource 在一定程度上解决了传递多个参数的问题,它将每个字符串解析为一条记录,然后作为参数逐字段传递。对于长字符串和/或大量参数,这很容易造成阅读困难。此外,可以使用的类型也受到了限制--稍后再详述。
在注解中声明实际值的所有来源都被限制为编译时常量(Java 注释的限制,而非 JUnit)。
@MethodSource 和 @ArgumentsSource 提供了(无类型的)n 个元组的流/集合,这些元组将作为方法参数传递。我们支持各种实际类型来表示 n 个元组的序列,但它们都不能保证符合方法的参数列表。这种源需要额外的方法或类,但对从哪里和如何获取测试数据没有限制。

正如您所看到的,可用的源类型从简单类型(使用简单,但功能有限)到最终需要更多代码才能运行的灵活类型不等。

Sidenote --这通常是良好设计的标志:只需少量代码就能实现基本功能,而当用于实现要求更高的用例时,增加额外的复杂性是合理的。 @EnumSource 似乎并不符合这种从简单到灵活的连续性假设。请看这个包含四个参数集的非复杂示例,每个参数集有两个值。

Note --虽然 @EnumSource 将枚举的值作为单个测试方法参数传递,但从概念上讲,测试是由枚举字段参数化的,因此对参数数量没有限制。

    enum Direction {
         UP(0, '^'),
         RIGHT(90, '>'),
         DOWN(180, 'v'),
         LEFT(270, '<');

        private final int degrees;
         private final char ch;

        Direction(int degrees, char ch) {
             this.degrees = degrees;
             this.ch = ch;
         }
     }

    @ParameterizedTest
     @EnumSource
     void direction(Direction dir) {
         assertEquals(0, dir.degrees % 90);
         assertFalse(Character.isWhitespace(dir.ch));
        
         int orientation = player.getOrientation();
         player.turn(dir);
         assertEquals((orientation + dir.degrees) % 360, player.getOrientation());
     }

试想一下:硬编码的值列表严重限制了它的灵活性(不能使用外部数据或生成数据),而声明枚举所需的额外代码量又使它比 @CsvSource 更为冗长。

但这只是第一印象。我们将看到,当利用 Java 枚举的真正威力时,它会变得多么优雅。

附注:本文不涉及验证生产代码中的枚举。当然,无论您选择何种验证方式,都必须声明这些枚举。本文的重点是何时以及如何以枚举的形式表达测试数据。


何时使用
在某些情况下,枚举的性能比其他方法更好:

每次测试多个参数
       当您只需要一个参数时,您可能不想让 @ValueSource 以外的事情变得复杂。但一旦您需要多个参数,例如输入和预期结果,您就必须使用 @CsvSource、@MethodSource/@ArgumentsSource 或 @EnumSource。

在某种程度上,枚举可以让你 "偷渡 "任意数量的数据字段。

因此,当您将来需要添加更多测试方法参数时,只需在现有的枚举中添加更多的字段,而无需触动测试方法签名。当你在多个测试中重复使用数据提供者时,这一点就变得非常重要。

对于其他数据源,必须使用 ArgumentsAccessors 或 ArgumentsAggregators 才能获得枚举开箱即用的灵活性。

类型安全
对于 Java 开发人员来说,这应该是一个大问题。

从 CSV(文件或字面)、@MethodSource 或 @ArgumentsSource 读取的参数无法在编译时保证参数数量及其类型与签名相匹配。

显然,JUnit 会在运行时发出抱怨,但 IDE 不会提供任何代码帮助。

和以前一样,当你在多个测试中重复使用相同的参数时,这种情况就会增加。在将来扩展参数集时,使用类型安全的方法将大有裨益。

自定义类型
对于基于文本的数据源(如从 CSV 读取数据的数据源)来说,这主要是一个优势--文本中编码的值需要转换为 Java 类型。

如果要从 CSV 记录中实例化一个自定义类,可以使用 ArgumentsAggregator 来实现。但是,您的数据声明仍然不是类型安全的--方法签名和声明数据之间的任何不匹配都会在运行时 "聚合 "参数时弹出。更不用说,声明聚合器类会增加参数化工作所需的更多支持代码。为了避免额外的代码,我们更倾向于使用 @CsvSource 而不是 @EnumSource。

可文档化
      与其他方法不同,枚举源为参数集(枚举实例)及其包含的所有参数(枚举字段)都提供了 Java 符号。它们为以 JavaDoc 这种更自然的形式附加文档提供了一个直接的位置。

文档并非不能放置在其他地方,但顾名思义,它将被放置在离文档更远的地方,因此更难找到,也更容易过时。

但还有更多枚举。是类。

许多初级开发人员还没有意识到 Java 枚举的真正强大之处。

在其他编程语言中,枚举只是美化了的常量。但在 Java 中,它们是 Flyweight 设计模式的便捷小实现,具有完整类的(大部分)优点。

为什么这是件好事呢?

测试与夹具相关的行为

     与其他任何类一样,枚举可以添加方法。如果枚举测试参数在不同测试间重复使用,这就变得非常方便--相同的数据,只是测试方式略有不同。为了在不进行大量复制和粘贴的情况下有效使用参数,这些测试之间还需要共享一些辅助代码。这不是一个辅助类和几个静态方法所能 "解决 "的。附注:请注意,这样的设计会让人产生 "功能嫉妒"(Feature Envy)。测试方法--或者更糟糕的是,辅助类方法--必须从枚举对象中提取数据,才能对这些数据执行操作。虽然这是过程式编程的(唯一)方法,但在面向对象的世界里,我们可以做得更好。 在枚举声明中直接声明 "辅助 "方法,我们就可以把代码移到数据所在的地方。或者,用 OOP 术语来说,辅助方法将成为以枚举形式实现的测试夹具的 "行为"。这不仅会使代码更加习以为常(在实例上调用合理的方法而不是传递数据的静态方法),而且还能使枚举参数在测试用例中更容易地重复使用。

继承
枚举可以实现带有(默认)方法的接口。在合理使用的情况下,可以利用这一点在多个数据提供者(多个枚举)之间共享行为。我很容易想到的一个例子就是为正向测试和负向测试分别建立枚举。如果它们代表了类似的测试夹具,那么它们就有可能共享某些行为。

Talk is cheap空谈误国

     让我们用一个假设的源代码文件转换器测试套件来说明这一点,它与执行 Python 2 到 Python 3 转换的测试套件不太一样。要想对这样一个综合工具的工作有真正的信心,我们最终需要大量的输入文件,这些文件体现了语言的各个方面,还需要匹配文件来比较转换结果。除此之外,还需要验证有问题的输入会向用户发出哪些警告/错误。 由于需要验证的样本数量庞大,这自然适合参数化测试,但由于数据有些复杂,它不太适合任何简单的 JUnit 参数源。

   enum Conversion {
         CLEAN("imports-correct.2.py", "imports-correct.3.py", Set.of()),
         WARNINGS("problematic.2.py", "problematic.3.py", Set.of(
                 "Using module 'xyz' that is deprecated"
         )),
         SYNTAX_ERROR("syntax-error.py", new RuntimeException("Syntax error on line 17"));
         // Many, many others ...

        @Nonnull
         final String inFile;
         @CheckForNull
         final String expectedOutput;
         @CheckForNull
         final Exception expectedException;
         @Nonnull
         final Set<String> expectedWarnings;

        Conversion(@Nonnull String inFile, @Nonnull String expectedOutput, @NotNull Set<String> expectedWarnings) {
             this(inFile, expectedOutput, null, expectedWarnings);
         }

        Conversion(@Nonnull String inFile, @Nonnull Exception expectedException) {
             this(inFile, null, expectedException, Set.of());
         }

        Conversion(@Nonnull String inFile, String expectedOutput, Exception expectedException, @Nonnull Set<String> expectedWarnings) {
             this.inFile = inFile;
             this.expectedOutput = expectedOutput;
             this.expectedException = expectedException;
             this.expectedWarnings = expectedWarnings;
         }

        public File getV2File() { ... }

        public File getV3File() { ... }
     }

    @ParameterizedTest
     @EnumSource
     void upgrade(Conversion con) {

        try {
             File actual = convert(con.getV2File());
             if (con.expectedException != null) {
                 fail("No exception thrown when one was expected", con.expectedException);
             }
             assertEquals(con.expectedWarnings, getLoggedWarnings());
             new FileAssert(actual).isEqualTo(con.getV3File());
         } catch (Exception ex) {
             assertTypeAndMessageEquals(con.expectedException, ex);
         }
     }

使用枚举并不会限制数据的复杂程度。正如你所看到的,我们可以在枚举中定义几个方便的构造函数,因此声明新的参数集非常简洁。这就避免了使用长参数列表的情况,因为长参数列表中往往充满了许多 "空 "值(空值、空字符串或集合),让人不知道 7 号参数--也就是空值之一--究竟代表什么。请注意,枚举允许使用复杂类型(Set、RuntimeException),而没有任何限制或神奇的转换。传递此类数据也是完全类型安全的。实际上,你将有更多的数据样本需要验证,因此模板代码的数量相比之下就不那么重要了。

此外,还可以看看如何利用相同的枚举及其辅助方法编写相关测试:

    @ParameterizedTest
     @EnumSource
     // Upgrading files already upgraded always passes, makes no changes, issues no warnings.
     void upgradeFromV3toV3AlwaysPasses(Conversion con) throws Exception {
         File actual = convert(con.getV3File());
         assertEquals(Set.of(), getLoggedWarnings());
         new FileAssert(actual).isEqualTo(con.getV3File());
     }

    @ParameterizedTest
     @EnumSource
     // Downgrading files created by upgrade procedure is expected to always pass without warnings.
     void downgrade(Conversion con) throws Exception {
         File actual = convert(con.getV3File());
         assertEquals(Set.of(), getLoggedWarnings());
         new FileAssert(actual).isEqualTo(con.getV2File());
     }

    多说几句 从概念上讲,@EnumSource 鼓励您创建复杂的、机器可读的单个测试场景描述,模糊了数据提供程序和测试夹具之间的界限。将每个数据集表示为 Java 符号(枚举元素)的另一个好处是,它们可以单独使用,完全脱离数据提供程序/参数化测试。由于它们有一个合理的名称,而且自成一体(在数据和行为方面),因此它们有助于形成漂亮和可读的测试。

@Test
void warnWhenNoEventsReported() throws Exception {
     FixtureXmls.Invalid events = FixtureXmls.Invalid.NO_EVENTS_REPORTED;
    
     // read() is a helper method that is shared by all FixtureXmls
     try (InputStream is = events.read()) {
         EventList el = consume(is);
         assertEquals(Set.of(...), el.getWarnings());
     }
}

现在,@EnumSource 不会成为你最常用的参数源之一,这是好事,因为过度使用它不会有任何好处。但在适当的情况下,知道如何使用它们所提供的一切,还是很方便的。


今天先到这儿,希望对云原生,技术领导力, 企业管理,系统架构设计与评估,团队管理, 项目管理, 产品管管,团队建设 有参考作用 , 您可能感兴趣的文章:
领导人怎样带领好团队
构建创业公司突击小团队
国际化环境下系统架构演化
微服务架构设计
视频直播平台的系统架构演化
微服务与Docker介绍
Docker与CI持续集成/CD
互联网电商购物车架构演变案例
互联网业务场景下消息队列架构
互联网高效研发团队管理演进之一
消息系统架构设计演进
互联网电商搜索架构演化之一
企业信息化与软件工程的迷思
企业项目化管理介绍
软件项目成功之要素
人际沟通风格介绍一
精益IT组织与分享式领导
学习型组织与企业
企业创新文化与等级观念
组织目标与个人目标
初创公司人才招聘与管理
人才公司环境与企业文化
企业文化、团队文化与知识共享
高效能的团队建设
项目管理沟通计划
构建高效的研发与自动化运维
某大型电商云平台实践
互联网数据库架构设计思路
IT基础架构规划方案一(网络系统规划)
餐饮行业解决方案之客户分析流程
餐饮行业解决方案之采购战略制定与实施流程
餐饮行业解决方案之业务设计流程
供应链需求调研CheckList
企业应用之性能实时度量系统演变

如有想了解更多软件设计与架构, 系统IT,企业信息化, 团队管理 资讯,请关注我的微信订阅号:

使用 JUnit 5.7 进行参数化测试:深入了解 @EnumSource

作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 该文章也同时发布在我的独立博客中-Petter Liu Blog。文章来源地址https://www.toymoban.com/news/detail-840775.html

到了这里,关于使用 JUnit 5.7 进行参数化测试:深入了解 @EnumSource的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • IDEA使用Junit4进行单元测试

            单元测试不仅能帮我们验证代码逻辑,还可以通过执行时间进行快速筛选不合格代码并进行优化。核心模块的代码的单元测试覆盖率要达到100%,其他模块代码的单元测试覆盖率需要达到60%。下面将介绍在IDEA上使用Junit4进行单元测试的方法: 一、安装JunitGenerator2插件

    2023年04月16日
    浏览(43)
  • 在Idea中使用Junit4进行单元测试

    #初学者,为了对知识进行巩固写这个文章,有问题欢迎指出,欢迎讨论 在Setting - Plugins - Marketplace中搜索JUnit,点击Apply后重启Idea 创建的类和文件夹 按照这个顺序就可以啦 点击OK后,在我们的Test文件夹中就出现了一个测试文件

    2024年02月03日
    浏览(53)
  • spring boot 单元测试JUnit5使用断言Assertions和假定Assumptions、嵌套、参数测试

    spring boot 单元测试JUnit5使用断言Assertions和假定Assumptions、嵌套、参数测试 本文基于spirng boot 2.7.11, 大家注意自己的版本 Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库 SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖 源码地址:https://gitcode.net/qq_39339588/springboot.git 1.

    2024年02月11日
    浏览(49)
  • 在 Java 中使用JUnit5进行单元测试和自动化测试

    单元测试和自动化测试是现代软件开发过程中必不可少的环节,可以提高代码质量和开发效率。JUnit5是Java中流行的单元测试框架,本文将介绍如何在Java中使用JUnit5进行单元测试和自动化测试。 2.1 单元测试的基本概念和原理 单元测试是一种测试方法,用于对软件系统中的最

    2024年02月03日
    浏览(45)
  • 深入了解 Java 方法和参数的使用方法

    方法是一块仅在调用时运行的代码。您可以将数据(称为参数)传递到方法中。方法用于执行特定的操作,它们也被称为函数。 重用代码:定义一次代码,多次使用。 提高代码的结构化和可读性。 将代码分解成更小的模块,易于维护和理解。 方法必须在类内声明。它的定义

    2024年02月19日
    浏览(47)
  • 如何在 Java 中使用JUnit5进行单元测试和自动化测试

    单元测试和自动化测试是现代软件开发过程中必不可少的环节,可以提高代码质量和开发效率。JUnit5是Java中流行的单元测试框架,本文将介绍如何在Java中使用JUnit5进行单元测试和自动化测试。 单元测试是一种测试方法,用于对软件系统中的最小可测试单元进行测试。这些单

    2024年02月05日
    浏览(57)
  • 在一个maven项目中使用maven命令进行junit单元测试

    如何在一个maven项目中使用maven命令进行junit单元测试? 首先确定一个maven项目的结构: 包含源代码目录src/main/java. 配置目录src/main/resources. 测试代码目录src/test. 目录结构可视化如下: 假定demo.java内容如下所示 我们要测试这个demo类, 可以在demoTest.java中写以下代码 然后就可以通过

    2023年04月21日
    浏览(55)
  • Spring Boot使用JUnit和Mockito进行Service层单元测试

      在平时的开发当中,一个项目往往包含了大量的方法,可能有成千上万个。如何去保证这些方法产生的结果是我们想要的呢?那么在SpringBoot项目中我们是如何对Service层的方法进行测试的呢?   单元测试是一种软件测试方法,用于测试代码的最小可测试单元。在Java开发

    2024年02月03日
    浏览(56)
  • 单元测试 —— JUnit 5 参数化测试

    目录 设置 我们的第一个参数化测试 参数来源 @ValueSource @NullSource @EmptySource @MethodSource @CsvSource @CsvFileSource @EnumSource @ArgumentsSource 参数转换 参数聚合 奖励 总结 如果您正在阅读这篇文章,说明您已经熟悉了JUnit。让我为您概括一下JUnit——在软件开发中,我们开发人员编写的代

    2024年02月03日
    浏览(46)
  • java的junit之异常测试、参数化测试、超时测试

    异常本身是方法签名的一部分 测试错误的输入是否导致特定的异常 summary 测试异常可以使用@Test(expected=Exceptio.class) 对可能发生的每种类型的异常进行测试 如果待测试的输入和输出是一组数据: 可以把测试数据组织起来 用不同的测试数据调用相同的测试方法 可以为Junit的单

    2024年02月14日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包