简述使用混合传递参数时的基本原则_JUnit 5参数化方法测试(一)
参数化简述
参数化允许我们为测试方法提供数据源(泛指数据集),平常我们使用的最多的或许是不带任何参数的形式,所有的必要数据都在类中或是方法中的本地变量亦或是直接常量写死了,这就导致测试变得较为繁琐,当我们需要对不同的数据做测试时往往需要修改变量值之后再次执行测试用例。当然,一个比较直接的解决方案是:将方法中的核心逻辑抽取成一个普通方法,并将易变的量提取成方法参数,而然后在测试方法中多次调用(提供不同参数值)即可。简而言之,参数化就是把易变量提取成方法参数传递,将数据与逻辑分离。
@ParameterizedTest(声明一个参数化测试方法)
JUnit
中的参数化测试就是为了解决此类问题,这里使用的是JUnit
大版本是5,注解@ParameterizedTest
是参数化测试的主要注解,声明一个方法是参数化测试方法,除此之外它还可指定每条测试结果的方法的显示格式(显示结果参见Idea
测试视图Test Results
节点树),如果你在测试方法指定了这个注解,那么必须去掉@Test
注解,否则抛出异常ParameterResolutionException: No ParameterResolver registered for parameter
;配合@XxxSource
注解提供的数据源,可以执行批量测试:
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) // 可标注在注解和方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TestTemplate
@ExtendWith({ParameterizedTestExtension.class})
public @interface ParameterizedTest {// 方法签名,形如:testXxx(Integer)String DISPLAY_NAME_PLACEHOLDER = "{displayName}"; // 执行索引,从1开始String INDEX_PLACEHOLDER = "{index}"; // 方法实参String ARGUMENTS_PLACEHOLDER = "{arguments}"; // 以键值对的形式显示参数值,形如:value=1,键固定是valueString ARGUMENTS_WITH_NAMES_PLACEHOLDER = "{argumentsWithNames}"; // 默认的显示格式String DEFAULT_DISPLAY_NAME = "[{index}] {argumentsWithNames}";String name() default "[{index}] {argumentsWithNames}";
}
@XxxSource
注解主要(另三个注解:@NullAndEmptySource
、@NullSource
、EmptySource
用处不大,在此不费篇幅)有@ValueSource
、@CsvSource
、@CsvFileSource
、@MethodSource
、@ArgumentsSource
以及@EnumSource
,下面分别介绍。
@ValueSource(提供单类型数据)
参数化测试至少需要一个方法参数来接收数据源中的数据,对于简单的数据可以使用@ValueSource
注解提供,这个注解支持我们提供众多基本数据类型数据,见名知意,不过多赘述:
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ArgumentsSource(ValueArgumentsProvider.class)
public @interface ValueSource {short[] shorts() default {};byte[] bytes() default {};int[] ints() default {};long[] longs() default {};float[] floats() default {};double[] doubles() default {};char[] chars() default {};boolean[] booleans() default {};String[] strings() default {};Class<?>[] classes() default {};
}
结合@ParameterizedTest
注解可这样使用:
class ParamTests {// 重新定义了方法名的输出格式:[1] value=1 : 1 - testParameterized(Integer)@ParameterizedTest(name = "[{index}] {argumentsWithNames} : {arguments} - {displayName}")@ValueSource(ints = {1, 3, 4, 5}) // 这些值将会被自动传给参数value,数组的长度便是方法执行的次数void testParameterized(Integer value) {}
}
需要注意的是@ValueSource
注解你需要明确一个类型,同时提供多个类型的源是不允许的:
class ParamTests {@ParameterizedTest// 错误用法@ValueSource(ints = {1, 3, 4, 5}, strings = {"arg1", "arg2", "arg3", "arg4"})void testParameterized(Integer value, String arg) {}
}
@CsvSource(提供多类型数据)
上面的需要提供多种类型数据的情形还是有很大需求的,对此JUnit
提供了@CsvSource
注解来支持。它的value()
方法接收一个String
类型数组,每个数组元素就是一行数据,一行数据中可以用逗号","分割多个参数值(或称为列),声明如下:
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ArgumentsSource(CsvArgumentsProvider.class)
public @interface CsvSource {String[] value();// 默认逗号分隔符,单个字符char delimiter() default '0'; // 如果你需要使用字符串作为分隔符就指定一个值String delimiterString() default ""; // 指定这个值将替换value字符串中的单引号'',相当于单引号''是占位符,这个才是真正的值String emptyValue() default "";// 数据中的列值(逗号分隔)如果在给定的nullValues中的,都将被置为null,而不是value给定的值String[] nullValues() default {};
}
例如我们有如下方法:
void testParameterized(Integer value, String arg) {}
上述方法有存在多个参数,所以我们需要提供的数据源如下:
class ParamTests {@ParameterizedTest@CsvSource(value = {"arg1, 1", "arg2, 2"})void testParameterized(String arg, Integer value) {}
}
需要注意的是,JUnit
会自动对分割后的结果进行类型转换后传递给方法参数, 而传递的顺序是参照数据的编写顺序的,因此对于方法:void testParameterized(String arg, Integer value)
,如果数据源的编写顺序是:@CsvSource(value = {"1,arg1", "2,arg2"})
,那么将会导致类型转换失败(JUnit
会尝试根据方法的参数类型对提供的数据进行自动转换)!
此外,对于一些数据行中的一些列有时不需要提供值,但你必须以逗号代替:@CsvSource(value = {"arg1, 1, 1", "arg2,,"})
第一个值之后,一个逗号对应一个列,即对应到方法中的一个参数。
@CsvFileSource(Csv文件提供多类型大批量数据)
@CsvSource
注解已能满足绝大多数需求,但它明显不适合大批量数据。对于大批量的数据我们往往是定义在文件中,这样很好的跟代码解耦,非常灵活。@CsvFileSource
注解就是为此而生:
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ArgumentsSource(CsvFileArgumentsProvider.class)
public @interface CsvFileSource {// csv文件的位置String[] resources();// 文件编码String encoding() default "UTF-8";// 换行符表示一行数据的结束String lineSeparator() default "n";// 默认列还是逗号分隔char delimiter() default '0'; // 列数据的分隔字符串String delimiterString() default "";// 如有跳过任意行数据的需求可指定int numLinesToSkip() default 0;// 指定这个值将替换csv文件中的双引号"",相当于双引号""是占位符,这个才是真正的值String emptyValue() default "";// csv文件中的列值(逗号分隔)如果在给定的nullValues中的,都将被置为null,而不是文件中给定的值String[] nullValues() default {};
}
这是一个名为data.csv
的文件数据:
arg1,1
arg2,2
arg3,3
这个Csv
文件在classpath
下,可以这样使用:
class ParamTests {@ParameterizedTest@CsvFileSource(resources = "/data.csv")void testParameterized(String arg, Integer value) {}
}
@MethodSource(方法供给数据)
这种方式需要在测试类内定义一个无参静态方法供给数据,默认情况下如果不指定@MethodSource
注解的value
,静态方法的名称必须与测试方法同名,若不同名则需指定静态供给方法的方法名(推荐手动指定,方便Idea侦测,避免警告):
class ParamTests {@ParameterizedTest@MethodSource("provider")void testParameterized(String arg, Integer value) {}static List<Arguments> provider() {return List.of(Arguments.of("arg1", 1), Arguments.of("arg2", 2));}
}
上面我们用org.junit.jupiter.params.provider.Arguments
接口的静态方法of(Object... arguments)
构造参数,你可以认为一个Arguments
对象就是csv
文件中的一行数据。
对于定义在外部类的静态无参方法也是可以的,但需要在@MethodSource
指定方法的全限定名形如:com.example.provider.DataProvider#provider
,略显麻烦。
此外,Stream
类型也得到了支持,对于单类型的数据,DoubleStream
、IntStream
及LongStream
都可直接使用,你可以:
class ParamTests {@ParameterizedTest@MethodSource("provider")void testIntStream(Integer value) {}static IntStream provider() {return IntStream.rangeClosed(1, 10);}
}
多类型的数据,你可以使用Arguments
的of
或arguments
方法构造,然后Stream
包装:
class ParamTests {@ParameterizedTest@MethodSource("provider")void testIntStream(String argName, Integer value) {}static Stream<Arguments> provider() {return Stream.of(Arguments.of("arg1", 1),Arguments.of("arg2", 2));}
}
@ArgumentSource(ArgumentsProvider实现类供给数据)
这种方式通过一个org.junit.jupiter.params.provider.ArgumentsProvider
类型的实现类来获取数据:
public interface ArgumentsProvider {Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception;
}
你可以在测试类中提供一个静态实现类或者在外部提供一个普通的实现类,这里使用静态内部类:
class ParamTests {@ParameterizedTest@ArgumentsSource(DataProvider.class)void testParameterized(String arg, Integer value) {}static class DataProvider implements ArgumentsProvider {@Overridepublic Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {return Stream.of(Arguments.of("arg1", 1), Arguments.of("arg2", 2));}}
}
此外通过这个注解的源码可以发现它是个可重复注解:
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(ArgumentsSources.class) // 可重复注解
public @interface ArgumentsSource {Class<? extends ArgumentsProvider> value();
}
这意味着你可以在一个方法上多次使用@ArgumentsSource
注解,当然,在@ArgumentsSources
注解(接收ArgumentsSource
数组)里面使用多个@ArgumentsSource
注解是更为推荐的方式,在此不再赘述。
@EnumSource(枚举类供给数据)
从一个指定的枚举类中获取数据,这就枚举类通过value
指定,除此之外它还提供了数据筛选功能,方便我们提取需要的数据,而不是全部的枚举类中的值。该注解源码过长,在此只给出必要的部分(请注意这个注解是使用到了@ArgumentsSource
注解的,核心逻辑就在EnumArgumentsProvider
类中,这里不再展开):
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ArgumentsSource(EnumArgumentsProvider.class)
public @interface EnumSource {// 要使用的枚举类Class<? extends Enum<?>> value() default NullEnum.class;// 要作为筛选条件的枚举类中的枚举值String[] names() default {};// 筛选的方式,默认是包含,即找到 names数组中指定的枚举值Mode mode() default Mode.INCLUDE;// 筛选的模式enum Mode { // ...}
}
有几点必须要提一下,拿names
来说,假如我们有以下枚举类:
enum Season {SPRING, SUMMER, FALL, WINTER
}
如果想要找到包含SPRING
和WINTER
的枚举值,那么代码需要这样写:
@ParameterizedTest@EnumSource(value = Season.class, names = {"SPRING", "WINTER"}, mode = EnumSource.Mode.INCLUDE)void testSeasonEnum(Season season) {}
然后是mode
,其值是@EnumSource
注解中的一个枚举类,其值有INCLUDE
(只获得符合names
指定条件的枚举值)、EXCLUDE
(除去names
指定条件的其它所有枚举值)、MATCH_ALL
(完全匹配names
中指定的所有值的项才会返回)以及MATCH_ANY
(匹配names
指定的任意值的项即可返回)。最后,在names
中是支持使用正则表达式的。
软件版本
软件 | 版本 |
---|---|
JUnit | 5.6.2(junit-jupiter) |
结语
本文介绍了JUnit
5参数化测试的一些基本使用,因篇幅原因一些较高级的知识点将在下文《JUnit
5参数化方法测试(二)》中讲述。
简述使用混合传递参数时的基本原则_JUnit 5参数化方法测试(一)相关推荐
- 简述使用混合传递参数时的基本原则_过程控制系统与仪表习题答案 -
6-4 过程控制系统设计包括哪些步骤? 解答:P174 (1)熟悉和理解生产对控制系统的技术要求与性能指标 (2)建立被控过程的数学模型 (3)控制方案的确定 (4)控制设备选型 (5)实验(或仿真) ...
- 简述使用混合传递参数时的基本原则_工程机械考试题库
练习一 一.填空 1.装载机按使用场所不同可以分为露天用装载机和井下用装载机. 2.同一铲斗有两种容积标志,一是平装斗容,二是堆装斗容. 3.半轴的理想支承方式是全浮式支承,这种支承方式可使半轴只承受 ...
- 简述使用混合传递参数时的基本原则_东南大学传热学考试真题试卷与解析
东大2006-2007学年第二学期期末考试 <传热学>试题(A卷)答案 一.填空题(每空1分,共20分) 1.某物体温度分布的表达式为t=f(x ,y,τ),此温度场为二维(几维).非稳态 ...
- 简述使用混合传递参数时的基本原则_化工原理复习资料
<化工原理>复习资料 一.选择题 1.下列单元操作中属于动量传递的有① ①流体输送,②蒸发,③气体吸收,④结晶 2.在26 ℃和1大气压下,CO2在空气中的分子扩散系数D 等于0.164c ...
- 解决JS在url中传递参数时参数包含中文乱码的问题
解决JS在url中传递参数时参数包含中文乱码的问题 参考文章: (1)解决JS在url中传递参数时参数包含中文乱码的问题 (2)https://www.cnblogs.com/xushengguan/ ...
- android fragment传递参数_fragment之间传值的两种方法
在Activity中加载Fragment的时候.有时候要使用多个Fragment切换.并传值到另外一个Fragment.也就是说两个Fragment之间进行参数的传递.查了很多资料.找到两种方法.一种 ...
- js向php传递中文参数,JS传递参数时对中文进行编码和解码
Nginx 配置简述 不论是本地开发,还是远程到 Server 开发,还是给提供 demo 给人看效果,我们时常需要对 Nginx 做配置,Nginx 的配置项相当多,如果考虑性能配置起来会比较麻烦. ...
- Get传递参数时发生java.lang.IllegalArgumentException异常
功能:售后地址的验证 当用户点击某地址时,将地址信息传递到后台,再从后台调用接口,判断用户地址填写是否正确 解析地址功能的实现层代码(简化过的) publicvoidaddressResolution ...
- 黄聪:解决Jquery在GET方式传递参数时gb2312中文编码乱码
数据传递编码仅支持UTF-8,这个虽然是全球统一编码,但是也要考虑一下中国人名本地网站的编码问题吧 而且 在jquery的发送端,无论你设置了程序级编码还是页面级编码,jquery都会使用utf-8的 ...
最新文章
- 巴塞罗那自治大学3D视觉课件
- 先定一个小目标,自己封装个ajax
- Leetcode 63. 不同路径 II (每日一题 20210903)
- docker 安装zookeeper集群
- Python3-笔记-B-003-数据结构-元组tuple( )
- 微软Silverlight==跨浏览器、跨客户平台的技术
- matlab meshc函数_MATLAB三维图形
- 华为否认鸿蒙为噱头;谷歌公布 6 大 iOS 漏洞;GitLab 又发安全补丁 | 极客头条...
- JS设为首页、添加到收藏夹
- 光纤温度传感器在电力系统的应用
- caxa 二次开发 应用程序框架分析
- 手撸springmvc乞丐版
- 一次编写命令时遇到的问题,Ambiguous method call.both
- test English
- 2021年度上海公务员考试公告(11月05日开启)
- 基于vue2编写的md编辑器-Bytemd
- java gif等比例缩放_对gif动图进行缩放等处理(java)
- java 计算器 正负号转换_java新手自己实现的计算器,有点乱
- 【沥血整理】灰度(二值)图像重构算法及其应用(morphological reconstruction)。...
- avrc语言的头文件和c文件,AVR(ATmega8L)单片机C语言引用头文件问题(新手问题)...
热门文章
- Java IO: 管道
- Ubuntu下通过 PPA 安装 Komodo 编辑器
- LVM的一些问题汇总 tune2fs命令
- 37、iamgeview 图层叠加
- idea java编译报错_intellij-idea,java_idea 编译报错,intellij-idea,java - phpStudy
- php5.5 php5.6,php 5.5 5.6的区别有哪些
- 用于生成随机数的python标准库模块是_17 Python 标准库之 random 模块 - Python 进阶应用教程...
- 第一章计算机基础知识第一节,第一章 计算机基础知识 第一节
- 第十七届全国大学生智能车竞赛开始啦
- 智能车竞赛技术报告 | 双车接力组 - 大连海事大学 - 同舟拾贰队