JUnit 5 –参数化测试

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/350627.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RMQ问题-ST表倍增处理静态区间最值

简介 ST表是利用倍增思想处理RMQ问题&#xff08;区间最值问题&#xff09;的一种工具。 它能够做到O(nlogn)预处理&#xff0c;O(1)查询的时间复杂度&#xff0c;效率相当不错。 算法 1.预处理 ST表利用倍增的思想。以洛谷的P3865作为例子。我们需要查询某一区间的最大值。 我…

tf.layers.dropout

dropout 是指在深度学习网络的训练过程中&#xff0c;对于神经网络单元&#xff0c;按照一定的概率将其暂时从网络中丢弃&#xff0c; 可以用来防止过拟合&#xff0c;layers 模块中提供了 tf.layers.dropout() 方法来实现这一操作&#xff0c;定义在 tensorflow/python/layers…

nginx是干嘛用的_nginx小技巧 -非root身份运行nginx

简直罪过&#xff0c;写这篇文章完全是一场毫无意义的口水仗引起的&#xff0c;我这人就这样&#xff0c;喜欢拿事实说话&#xff0c;而不是一句话说的让人摸不着头脑&#xff01;下载源码文件&#xff1a;wget http://nginx.org/download/nginx-1.16.1.tar.gz解压&#xff1a;…

pooling池化

pooling&#xff0c;即池化&#xff0c;layers 模块提供了多个池化方法&#xff0c;这几个池化方法都是类似的&#xff0c;包括 tf.layers.max_pooling1d()、tf.layers.max_pooling2d()、tf.layers.max_pooling3d()、tf.layers.average_pooling1d()、tf.layers.average_pooling…

mysql 数据迁移_MySQL海量数据迁移

数据库迁移本主前一段时间写毕业设计主要使用MySQL&#xff0c;紧锣密鼓的开发了将近一个多月&#xff0c;项目数据层、接口层、数据采集层已经开发完成&#xff0c;算法还在不断的优化提速&#xff0c;由于请了几位大佬帮我做Code Review&#xff0c;所以不得已购买了一个阿里…

数组中重复的数字

解决问题思路1. 代码实现&#xff1a; package j2;import java.util.Arrays;/*** Created by admin on 2019/5/8.*/ public class FindDuplicate {public static void duplicate(int[] numbers,int length,int[]duplication){//边界条件的判断if (numbers null || length0) {r…

eclipse中junit_在Eclipse中有效使用JUnit

eclipse中junit最近&#xff0c;我被卷入了讨论1和一些受感染的同伴2&#xff0c;他们关于我们如何在Eclipse IDE中使用JUnit 。 令人惊讶的是&#xff0c;对话带来了并非所有人都知道的一些“技巧”。 这使我有了写这篇文章的想法&#xff0c;总结了我们的演讲。 谁知道–也许…

BFS迷宫问题模型(具体模拟过程见《啊哈算法》)

题目描述与DFS模型走迷宫那篇一样。小哈被困在迷宫里&#xff0c;小哼解救小哈。 这里用BFS来写。BFS&#xff08;广搜&#xff09;与DFS&#xff08;深搜&#xff09;的区别就在于&#xff0c;DFS是“不撞南墙不回头”&#xff0c;一条路走到不能再走之后才会回到起始点&#…

Spring Batch可重启性

首先&#xff0c;我要非常感谢Spring的优秀人员&#xff0c;他们花了无数时间来确保Spring Batch作业的可行性&#xff0c;以及发出重新启动作业的神奇能力&#xff01; 感谢您提供的这个优雅的工具集&#xff0c;它使我们能够浏览大量数据集&#xff0c;同时使我们在跌倒时能够…

tf.clip_by_value()

tf.clip_by_value(A, min, max) 输入一个张量A&#xff0c;把A中的每一个元素的值都压缩在min和max之间。 小于min的让它等于min&#xff0c;大于max的元素的值等于max。

python如何导入seaborn_Seaborn - 导入数据集和库

教 程 目 录 在本章中&#xff0c;我们将讨论如何导入数据集和库.让我们首先了解如何导入库. 导入库 让我们从导入Pandas开始&#xff0c;这是一个管理关系的好库(表格式)数据集. Seaborn在处理DataFrames时非常方便&#xff0c;DataFrames是用于数据分析的最广泛使用的数据结构…

Windows安装Redis(转!)

转自https://www.cnblogs.com/wxjnew/p/9160855.html “现在我已经走到了人生的十字路口边了&#xff0c;我相信&#xff0c;在已走过的人生道路中&#xff0c;我一直知道其中哪一条是正确的&#xff0c;是的&#xff0c;我一直坚信我知道。但是我却从未选择那些正确的道路&…

弃用Java的终结器

JDK-8165641 &#xff08;“ Deprecate Object.finalize”&#xff09;已打开以“ deprecate Object.finalize&#xff08;&#xff09; ”&#xff0c;因为“ finalizer本质上存在问题&#xff0c;使用finalizer可能会导致性能问题&#xff0c;死锁&#xff0c;挂起和其他问题…

python安装mysqlclient_Python-安装mysqlclient(MySQLdb)

mysqlclient&#xff08;也就是Python3版本的MySQLdb&#xff09;&#xff0c;性能比pymysql好&#xff0c;速度更快及PyMySQL的应用场景&#xff1b;所以一般大项目建议使用MySQLdb 使用pip安装 pip install mysqlclient 安装过程中可能会出现如下问题&#xff1a;解决方法如下…

tesorflow 填充‘same’与‘valid’

源码&#xff1a; #codingutf-8import tensorflow as tf# case 2 input tf.Variable(tf.random_normal([1, 256, 256, 3]))op1 tf.layers.conv2d(inputsinput, filters164, kernel_size(7, 7), strides(2, 2), padding"same", activationtf.nn.relu)op2 tf.layer…

hadoop中两种上传文件方式

记录如何将本地文件上传至HDFS中 前提是已经启动了hadoop成功&#xff08;nodedate都成功启动&#xff09; ①先切换到HDFS用户 ②创建一个user件夹 bin/hdfs dfs -mkdir /user &#xff08;hadoop目录下&#xff09; 多级创建的时候 hdfs dfs -mkdir -p /wifi/classify 查看创…

c语言科学计数法_C入门:C语言中数据的储存(上)

How Data is stored in computer memory?储存数据是计算机进行各种数据操作的基础&#xff0c;为了理解计算机可以对哪些数据进行怎样的操作&#xff0c;有必要了解数据在计算机中的储存方式。为了有效防止失真&#xff0c;提高数据的准确性和稳定性&#xff0c;计算机使用二进…

tf.layers.dense

tf.layers.dense( inputs, units, activationNone, use_biasTrue, kernel_initializerNone, bias_initializertf.zeros_initializer(), kernel_regularizerNone, bias_regularizerNone,activity_regularizerNone, trainableTrue, nameNone, reuseNone ) 各参数含义&#xff1a;…

java 修改 枚举类字段_枚举枚举和修改“最终静态”字段的方法

java 修改 枚举类字段在本新闻通讯中&#xff0c;该新闻通讯最初发表在Java专家的新闻通讯第161期中&#xff0c;我们研究了如何使用sun.reflect包中的反射类在Sun JDK中创建枚举实例。 显然&#xff0c;这仅适用于Sun的JDK。 如果需要在另一个JVM上执行此操作&#xff0c;则您…

tf.reduce_mean

tf.reduce_mean(input_tensor, axisNone, keep_dimsFalse, nameNone, reduction_indicesNone) 作用&#xff1a;沿着张量不同的数轴进行计算平均值。 参数&#xff1a;input_tensor: 被计算的张量&#xff0c;确保为数字类型。 axis: 方向数轴&#xff0c;如果没有…