JUnit 5令人印象深刻,尤其是当您深入研究扩展模型和体系结构时 。 但是从表面上讲,编写测试的地方,开发的过程比革命的过程更具进化性 – JUnit 4上没有杀手级功能吗? 幸运的是,至少有一个:参数化测试。 JUnit 5对参数化测试方法具有本机支持,并且具有允许使用同一主题的第三方变体的扩展点。 在本文中,我们将研究如何编写参数化测试-创建扩展将留待将来使用。

总览

这篇文章是有关JUnit 5的系列文章的一部分:

  • 设定
  • 基本
  • 建筑
  • 移民
  • 动态测试
  • 参数化测试
  • 扩展模型
  • 条件
  • 参数注入

本系列基于预发行版本Milestone 4,并且在发布新的里程碑或GA版本时会进行更新。 另一个很好的来源是《 JUnit 5用户指南》 。 您可以在GitHub上找到所有代码示例。

在整个这篇文章中,我将大量使用terms 参数和自变量 ,其含义并不相同。 根据维基百科 :

术语参数通常用于指代在函数定义中找到的变量,而参数指代传递的实际输入。

您好,参数化世界

参数化测试入门非常容易,但是在开始乐趣之前,您必须向项目添加以下依赖项:

  • 群组ID :org.junit.jupiter
  • 工件ID :junit-jupiter-params
  • 版本 :5.0.0-M4

然后,通过在@ParameterizedTest而不是@Test上声明带有参数和拍击的测试方法开始:

@ParameterizedTest
// something's missing - where does `word` come from?
void parameterizedTest(String word) {assertNotNull(word);
}

看起来不完整– JUnit如何知道参数字应采用哪些参数? 好吧,因为您为其定义了零参数,所以该方法将被执行零次,并且实际上JUnit报告了该方法的Empty测试套件。

为了使事情发生,您需要提供参数,您可以从中选择各种来源。 可以说,最简单的方法是@ValueSource:

@ParameterizedTest
@ValueSource(strings = { "Hello", "JUnit" })
void withValueSource(String word) {assertNotNull(word);
}

确实,现在测试执行了两次:一次是“ Hello”,一次是“ JUnit”。 在IntelliJ中,如下所示:

这就是开始进行参数化测试所需的一切!

对于现实生活中的使用,您应该了解@ParamterizedTest的来龙去脉(例如,如何命名),其他参数来源(包括如何创建自己的)以及到目前为止的更多知识。有点神秘的功能,称为参数转换器。 我们现在将研究所有这些。

参数化测试的来龙去脉

使用@ParameterizedTests创建测试很简单,但是要充分利用该功能,您需要了解一些细节。

测试名称

从上面的IntelliJ屏幕截图可以看出,参数化的测试方法显示为带有每个调用的子节点的测试容器。 这些节点的名称默认为“ [{index}] {arguments}”,但可以使用@ParameterizedTest设置其他名称:

@ParameterizedTest(name = "run #{index} with [{arguments}]")
@ValueSource(strings = { "Hello", "JUnit" })
void withValueSource(String word) { }

只要修剪后的字符串不为空,就可以将其用作测试的名称。 可以使用以下占位符:

  • {index}:从1开始计数测试方法的调用; 此占位符被替换为当前调用的索引
  • {arguments}:被方法的n个参数替换为{0},{1},…{n}(到目前为止,我们仅看到带有一个参数的方法)
  • {i}:被当前调用中第i个参数具有的参数替换

我们将在一分钟内介绍替代资源,因此暂时忽略@CsvSource的详细信息。 只需看看可以通过这种方式构建的出色测试名称,尤其是与@DisplayName一起使用 :

@DisplayName("Roman numeral")
@ParameterizedTest(name = "\"{0}\" should be {1}")
@CsvSource({ "I, 1", "II, 2", "V, 5"})
void withNiceName(String word, int number) {    }

非参数化参数

不管参数化测试如何,JUnit Jupiter都已经可以将参数注入测试方法中 。 只要将每次调用中变化的参数排在首位,这可以与参数化测试结合使用:

@ParameterizedTest
@ValueSource(strings = { "Hello", "JUnit" })
void withOtherParams(String word, TestInfo info, TestReporter reporter) {reporter.publishEntry(info.getDisplayName(), "Word: " + word);
}

与以前一样,此方法被调用两次,两次参数解析器都必须提供TestInfo和TestReporter的实例。 在这种情况下,这些提供程序已内置在Jupiter中,但是自定义提供程序(例如用于模拟)也将同样有效。

元注释

最后但并非最不重要的一点是,@ParameterizedTest(以及所有源代码)可以用作元注释来创建自定义扩展和注释 :

@Params
void testMetaAnnotation(String s) { }@Retention(RetentionPolicy.RUNTIME)
@ParameterizedTest(name = "Elaborate name listing all {arguments}")
@ValueSource(strings = { "Hello", "JUnit" })
@interface Params { }

参数来源

三种成分进行参数化测试:

  1. 有参数的方法
  2. @ParameterizedTest批注
  3. 参数值,即参数

参数由源提供,可以为测试方法使用任意数量的参数,但至少应有一个(否则测试将根本不会执行)。 存在一些特定的资源,但是您也可以自由创建自己的资源。

要理解的核心概念是:

  • 每个源都必须为所有测试方法参数提供参数(因此,第一个参数不能有一个源,第二个参数不能有另一个源)
  • 该测试将对每组参数执行一次

价值来源

您已经看到了@ValueSource的实际应用。 它使用起来非常简单,并且可以为几种基本类型输入安全类型。 您只需应用注释,然后从以下元素之一(也可以是其中一个)中进行选择:

  • String [] strings()
  • int [] ints()
  • long [] longs()
  • double [] doubles()

之前,我向您展示了字符串–在这里,您已经花费了很长时间:

@ParameterizedTest
@ValueSource(longs = { 42, 63 })
void withValueSource(long number) { }

有两个主要缺点:

  • 由于Java对有效元素类型的限制 ,它不能用于提供任意对象(尽管对此有一种补救方法-请等到阅读有关参数转换器的信息之后 )
  • 它只能用于具有单个参数的测试方法

因此,对于大多数非平凡的用例,您将不得不使用其他来源之一。

枚举来源

这是一个非常具体的资源,您可以使用它为一个枚举或其子集的每个值运行一次测试:

@ParameterizedTest
@EnumSource(TimeUnit.class)
void withAllEnumValues(TimeUnit unit) {// executed once for each time unit
}@ParameterizedTest
@EnumSource(value = TimeUnit.class,names = {"NANOSECONDS", "MICROSECONDS"})
void withSomeEnumValues(TimeUnit unit) {// executed once for TimeUnit.NANOSECONDS// and once for TimeUnit.MICROSECONDS
}

直截了当吧? 但是请注意,@ EnumSource只为一个参数创建参数,这与源必须为每个参数提供参数的事实相结合,这意味着它只能在单参数方法上使用。

方法来源

@ValueSource和@EnumSource非常简单,并且在一定程度上受到了限制–一般方法的另一端是@MethodSource。 它只是简单地命名将提供参数流的方法。 从字面上看:

@ParameterizedTest
@MethodSource(names = "createWordsWithLength")
void withMethodSource(String word, int length) { }private static Stream createWordsWithLength() {return Stream.of(ObjectArrayArguments.create("Hello", 5),ObjectArrayArguments.create("JUnit 5", 7));
}

Argument是一个包装对象数组的简单接口,ObjectArrayArguments.create(Object…args)从提供给它的varargs创建它的实例。 支持注释的类完成了其余工作,并且withMethodSource这样执行了两次:一次用word =“ Hello” / length = 5,一次用word =“ JUnit 5” / length = 7。

@MethodSource命名的方法必须是静态的,并且可以是私有的。 他们必须返回一种集合,该集合可以是任何Stream(包括原始的特殊性),Iterable,Iterator或数组。

如果源仅用于单个参数,则可能空白返回此类实例,而不将其包装在Argument中:

@ParameterizedTest
@MethodSource(names = "createWords")
void withMethodSource(String word) { }private static Stream createWords() {return Stream.of("Hello", "Junit");
}

就像我说的那样,@ MethodSource是Jupiter提供的最通用的资源。 但这会招致声明方法和将参数组合在一起的开销,这对于较简单的情况来说有点多。 最好使用两个CSV来源。

CSV来源

现在,它变得非常有趣。 能够在那时和那里为几个参数定义少数参数集而不必通过声明方法来很好吗? 输入@CsvSource! 使用它,您可以将每次调用的参数声明为以逗号分隔的字符串列表,并将其余参数留给JUnit:

@ParameterizedTest
@CsvSource({ "Hello, 5", "JUnit 5, 7", "'Hello, JUnit 5!', 15" })
void withCsvSource(String word, int length) { }

在此示例中,源标识了三组参数,从而导致了三个测试调用,然后继续将它们放在逗号上并将其转换为目标类型。 看到“'Hello,JUnit 5!',15”中的单引号吗? 这是使用逗号的方式,而不会在该位置将字符串切成两半。

将所有参数都表示为字符串会引起一个问题,即如何将它们转换为正确的类型。 我们待会儿会谈,但是在我想快速指出之前,如果您有大量输入数据,则可以将它们自由存储在外部文件中:

@ParameterizedTest
@CsvFileSource(resources = "word-lengths.csv")
void withCsvSource(String word, int length) { }

请注意,资源可以接受多个文件名,并将一个接一个地处理它们。 @CsvFileSource的其他元素允许指定文件的编码,行分隔符和定界符。

自定义参数来源

如果JUnit内置的源代码无法满足您的所有用例,则可以自由创建自己的用例。 我将不赘述-足以说明,您必须实现此接口…

public interface ArgumentsProvider {Stream<? extends Arguments> provideArguments(ContainerExtensionContext context) throws Exception;}

…,然后将您的源代码与@ArgumentsSource(MySource.class)或自定义注释一起使用 。 您可以使用扩展上下文访问各种信息,例如,调用源的方法,以便知道它有多少个参数。

现在,开始转换这些参数!

参数转换器

除了方法源之外,参数源只能提供非常有限的类型类型:字符串,枚举和一些基元。 当然,这不足以编写全面的测试,因此需要一条通往更丰富的类型环境的道路。 参数转换器就是那条路:

@ParameterizedTest
@CsvSource({ "(0/0), 0", "(0/1), 1", "(1/1), 1.414" })
void convertPointNorm(@ConvertPoint Point point, double norm) { }

让我们看看如何到达那里……

首先,一般观察:无论所提供的参数和目标参数具有哪种类型,都将始终要求转换器将其转换为另一种。 但是,只有前面的示例声明了一个转换器,那么在所有其他情况下会发生什么?

默认转换器

Jupiter提供了一个默认转换器,如果未应用其他转换器,则将使用它。 如果参数和参数类型匹配,则转换为空操作,但如果参数为字符串,则可以将其转换为多种目标类型:

  • char或Character(如果字符串的长度为1)(如果您使用UTF-32字符(如表情符号,因为它们包含两个Java字符),则可能会使您失望)
  • 其他所有原语及其包装类型以及它们各自的valueOf方法
  • 通过使用字符串和目标枚举调用Enum :: valueOf来获取任何枚举
  • 一堆时间类型,例如Instant,LocalDateTime等,OffsetDateTime等,ZonedDateTime,Year和YearMonth及其各自的解析方法

这是一个简单的示例,其中显示了其中一些操作:

@ParameterizedTest
@CsvSource({"true, 3.14159265359, JUNE, 2017, 2017-06-21T22:00:00"})
void testDefaultConverters(boolean b, double d, Summer s, Year y, LocalDateTime dt) { }enum Summer {JUNE, JULY, AUGUST, SEPTEMBER;
}

受支持的类型的列表可能会随着时间的推移而增长,但是很明显它不能包括特定于您的代码库的类型。 这是定制转换器输入图片的地方。

定制转换器

使用自定义转换器,您可以将源发出的参数(通常是字符串)转换为要在测试中使用的任意类型的实例。 创建它们很容易–您所需要做的就是实现ArgumentConverter接口:

public interface ArgumentConverter {Object convert(Object input, ParameterContext context)throws ArgumentConversionException;}

输入和输出是无类型的,这有点令人讨厌,但是,因为Jupiter知道两者都不是,所以在更具体的方面确实没有用。 您可以使用参数上下文获取有关要为其提供参数的参数的更多信息,例如参数的类型或最终将在其上调用测试方法的实例。

对于已经具有静态工厂方法(例如“(1/0)”)的Point类,convert方法非常简单:

@Override
public Object convert(Object input, ParameterContext parameterContext)throws ArgumentConversionException {if (input instanceof Point)return input;if (input instanceof String)try {return Point.from((String) input);} catch (NumberFormatException ex) {String message = input+ " is no correct string representation of a point.";throw new ArgumentConversionException(message, ex);}throw new ArgumentConversionException(input + " is no valid point");
}

Point的第一个检查输入实例有点麻木(为什么它已经是一个点了?),但是一旦我开始打开类型,就无法让自己忽略这种情况。 随时判断我。

现在,您可以使用@ConvertWith应用转换器:

@ParameterizedTest
@ValueSource(strings = { "(0/0)", "(0/1)","(1/1)" })
void convertPoint(@ConvertWith(PointConverter.class) Point point) { }

或者,您可以创建一个自定义批注以使其看起来不那么技术:

@ParameterizedTest
@ValueSource(strings = { "(0/0)", "(0/1)","(1/1)" })
void convertPoint(@ConvertPoint Point point) { }@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ConvertWith(PointConverter.class)
@interface ConvertPoint { }

这意味着,通过使用@ConvertWith或自定义注释对参数进行注释,JUnit Jupiter将传递提供给转换器的源的任何参数。 通常,您会将其应用于发出字符串的@ValueSource或@CsvSource之类的源,以便随后将其解析为您选择的对象。

反射

那是一个很大的旅程,所以让我们确保我们拥有一切:

  • 我们首先添加了junit-jupiter-params工件,然后将@ParameterizedTest应用于带有参数的测试方法。 在研究了如何命名参数化测试之后,我们开始讨论参数的来源。
  • 第一步是使用@ ValueSource,@ MethodSource或@CsvSource之类的源来为该方法创建参数组。 每个组都必须具有所有参数的参数(参数解析器中的参数除外),并且每个组将调用该方法一次。 可以实现自定义源并将其与@ArgumentsSource一起应用。
  • 由于源通常仅限于几种基本类型,因此第二步是将它们转换为任意类型。 默认转换器对原语,枚举和某些日期/时间类型执行此操作。 定制转换器可以与@ConvertWith一起应用。

这使您可以轻松地使用JUnit Jupiter参数化您的测试!

但是,这种特定机制很可能无法满足您的所有需求。 在这种情况下,您会很高兴听到它是通过扩展点实现的,可用于创建您自己的参数化测试的变体–我将在以后的文章中进行研究,敬请期待。

翻译自: https://www.javacodegeeks.com/2017/06/junit-5-parameterized-tests.html

JUnit 5 –参数化测试相关推荐

  1. junit 单元测试 - 参数化测试

    junit4.x版本需要引入如下jar包: hamcrest-core-1.3.jar junit-4.12-beta-3.jar 新建一个计算器类,如下: package com.pt;public ...

  2. 单元测试之JUnit 5 参数化测试使用手册

    1. 概要 junit5是下一代JUnit测试框架,新增了很多特性帮助开发人员更好得编写测试用例.其中一大特性就是参数化测试,其目的就是让我们可以使用不同的参数多次执行一个测试方法,从而覆盖不同的条件 ...

  3. 参数化测试 junit_使用JUnit 5进行更清洁的参数化测试

    参数化测试 junit 参数化单元测试的总体思路是对不同的数据运行相同的测试方法. 在JUnit 4中创建参数化测试远非完美. 现有体系结构存在许多问题:将参数定义为类字段,并需要使用构造函数来创建它 ...

  4. 使用JUnit 5进行更清洁的参数化测试

    参数化单元测试的总体思路是对不同的数据运行相同的测试方法. 在JUnit 4中创建参数化测试远非完美. 现有体系结构存在许多问题:将参数定义为类字段,并且需要使用构造函数来创建它们,不能将参数化和非参 ...

  5. 【Java单元测试】如何进行单元测试、异常测试、参数化测试、超时测试、测试多线程

    Junit单元测试的步骤 (1)新建一个单元测试 (2)选择位置 (3)选择需要测试的方法 (4)是否将Junit 4添加到ClassPath中 (5)自动生成的测试类 (6) 然后就可以编写单元测试 ...

  6. JUnit4学习笔记(二):参数化测试与假定(Assumption)

    一.一个简单的测试 编写一个只有一种运算的计算器: 1 public class Calculator { 2 public static double divide(int dividend, in ...

  7. 软件测试——JUnit中的参数化测试

    2019独角兽企业重金招聘Python工程师标准>>> 参数化测试用于当需要使用多组不同的测试数据测试同一个方法的时候. 使用参数化测试的要点: ① 为该测试方法专门生成一个新的类: ...

  8. 参数化测试 junit_参数化的JUnit测试

    参数化测试 junit 有时,您会遇到一个问题,就是尖叫使用"参数化"测试,而不是多次复制/粘贴相同的方法. 测试方法基本上是相同的,唯一改变的是传入的数据.在这种情况下,请考虑创 ...

  9. 参数化测试 junit_使用JUnitParams进行参数化的JUnit测试

    参数化测试 junit 参数化的单元测试用于在不同条件下测试相同的代码. 借助参数化的单元测试,我们可以建立一种测试方法,该方法从某个数据源中检索数据. 该数据源可以是测试数据对象,外部文件甚至数据库 ...

最新文章

  1. what should we learn from Magnetite?
  2. Linux下定时器使用
  3. neditor 自定义工具栏配置
  4. oracle创建job一年执行,Oracle快速创建定时job执行批量转储过程脚本参考案例
  5. 妙趣横生的算法--二叉树
  6. 所有C#程序员必须知道的13件事
  7. webRTC之gtest安装使用(十五)
  8. 《Joel On Software》读后
  9. css3 烟 蚊香_CSS3 冒烟的管道 | 烟囱
  10. C#坦克大战流程设计与源代码(1):基本对象类规划
  11. 4k视频写入速度要求_雷克沙高速SD存储卡允许4K视频拍摄
  12. Qt 给文本添加删除线 text-decoration
  13. 小丁带你走进git的世界二-工作区暂存区分支
  14. input获取焦点边框 outline属性
  15. 【Linux学习笔记】管理Linux操作系统:软件安装
  16. Unity Obi插件修改到支持URP
  17. IM系统数据库设计 前端逻辑处理
  18. 怎么计算计算机的网络地址,如何计算IP地址的网络号和主机号?
  19. leetcode 1905. 统计子岛屿(C++、java、python)
  20. 两分钟快速理解成本函数(cost function)

热门文章

  1. MySQL format()函数
  2. JTA 深度历险 - 原理与实现
  3. 深入Java集合学习系列:LinkedHashSet的实现原理
  4. 推荐算法-关联分析(关联规则)
  5. Linux入门(一)之权限指令系统管理
  6. 04-插入操作更新操作删除操作
  7. 最好的方法,是为你们量身定制!
  8. 服务器登陆显示其他用户,连接远程服务器显示其他用户
  9. kafka再均衡监听器测试
  10. java 8 Lambda 表达式(副作用)