
3.2 JUnit参数化测试
使用JUnit之前需要在项目中引入两个依赖包junit-jupiter-engine和junit-jupiter-params,如下所示。


其中,junit-jupiter-engine是JUnit 5的执行引擎,而junit-jupiter-params则用于提供JUnit参数化测试的能力。
3.2.1 使用@ValueSource注解
@ValueSource注解可提供单个数组作为测试数据,数组元素支持8种基本类型、String和Class。
仍然以登录测试用例为例,首先新建一个测试类LoginForJUnit,代码如下。

该测试类中的测试方法与第1章中用TestNG实现的测试方法功能上保持一致。
接下来使用@ValueSource将登录页面的URL抽离出来。

使用JUnit的参数化测试必须将@Test注解替换为@ParameterizedTest注解。
另外,@ValueSource注解中的数组长度决定了测试方法执行的次数,因此如果要想给多个参数提供测试数据,使用@ValueSource注解是无法实现的。
3.2.2 使用@NullSource、@EmptySource和@NullAndEmptySource注解
有几个比较特殊的注解,即@NullSource、@EmptySource和@NullAndEmptySource,以下对其分别进行介绍。
@NullSource和@EmptySource注解可分别用于传递null值和空值,它们可单独使用,也可同时使用。以下是同时使用的示例。

而@NullAndEmptySource注解是@NullSource和@EmptySource的组合注解,可同时提供null值和空值。

由于@NullSource、@EmptySource和@NullAndEmptySource注解只支持单个参数传递,因此它们最常用于结合@ValueSource注解使用,后者用于传递正常值,而前者用于传递异常值。
3.2.3 使用@EnumSource注解
@EnumSource注解可提供单个枚举作为测试数据。@EnumSource注解的value属性用于指定枚举(默认为NullEnum.class);names和mode属性需要配合使用,稍后再介绍。
首先在LoginForJUnit类中新建一个枚举,代码如下。

由于登录测试方法不适用于演示该参数化方式,因此笔者新建了一个测试方法,代码如下。

执行该测试方法会遍历传入Role枚举值作为测试数据。
如果names属性指定了A和B两个枚举值,mode取值为Mode.INCLUDE,则传参只会传递A和B两个值。mode属性支持Mode.INCLUDE(默认)、Mode.EXCLUDE、Mode.MATCH_ALL和Mode.MATCH_ANY 4种匹配模式。由于枚举类型的测试数据相对少见,这里只演示Mode.INCLUDE匹配模式,有兴趣的读者请自行查阅相关资料了解每种模式的区别。

由于mode属性的默认值为Mode.INCLUDE,因此上述代码省略了mode属性。执行该测试方法,则不会传入Role.ADMIN这个枚举值了,因为它没有包含在names属性指定的值中。
3.2.4 使用@MethodSource注解
@MethodSource注解的功能非常强大,它可提供单个或多个工厂方法。如果该工厂方法与测试方法在同一个类中,除非测试实例生命周期使用按类方式,否则必须为静态方法;如果该工厂方法与测试方法不在同一个类中,则必须为静态方法。另外,工厂方法不能包含参数。@MethodSource注解的value属性指定工厂方法的方法名。
1.工厂方法与测试方法在同一个类中
首先创建一个工厂方法用于提供测试数据,代码如下。

然后修改登录测试方法,将@NullAndEmptySource注解替换为@MethodSource注解,并指定工厂方法的方法名。

执行登录测试方法,可以观察到IMS的登录URL被正确传递到了测试方法中。
@MethodSource注解可以同时传递多组测试数据给测试方法,因此可以使用它来抽离用户名和密码的测试数据。
首先需要对工厂方法进行改造,在Stream的类型参数中不能再使用String类型了,应该换用Arguments。Arguments可看成一组参数,可对应于TestNG数据提供者中Object[][]的Object[]数组。改造后的工厂方法如下。

然后将登录测试方法中的用户名和密码修改为变量形式,代码如下。

再次执行该测试方法,可观察到URL、用户名和密码均被正确传递到了测试方法中。2.工厂方法与测试方法不在同一个类中
测试类中的@MethodSource注解可引用外部类中的工厂方法为测试方法提供测试数据。
首先新增一个外部类用于存放工厂方法,代码如下。

可以看到外部类中的工厂方法与之前的保持一致。
然后修改登录测试方法中@MethodSource注解的value值。

请注意value值的书写方式,即类名和工厂方法名之间以井号(#)进行了分隔。
再次执行登录测试方法,执行结果与之前一致。
3.2.5 使用@CsvSource注解
@CsvSource注解可提供单组或多组参数,如果需要提供多组参数,则需要使用分隔符(默认为英文逗号)分隔数据。@CsvSource注解的属性解释如下。
value:指定测试数据。
delimiter:指定分隔字符,默认为英文逗号。
delimiterString:指定分隔字符串,默认为空字符串。
emptyValue:指定替代测试数据中空字符串的字符串,默认为空字符串。
nullValues:指定应被解释为null值的字符串,默认没有。
修改登录测试方法,将@MethodSource注解修改为@CsvSource。

执行登录测试方法,执行结果不变。
可以使用delimiter属性指定分隔的字符,以下示例将分隔符指定为了“|”。

也可以使用delimiterString属性指定分隔的字符串,以下示例将分隔字符串指定为了“{$}”。

emptyValue属性适用于提供默认值,假设未提供登录的URL,可以用它来指定默认值。

执行登录测试方法,登录成功。
假设测试数据中有“NIL”,想被转换为null值,可以使用nullValues属性指定即可。

执行登录测试方法,从控制台可以看出“NIL”已经被成功替换,如图3-1所示。

图3-1 NIL被替换成了null
3.2.6 使用@CsvFileSource注解
@CsvFileSource注解与@CsvSource注解类似,只不过@CsvFileSource注解是通过读取CSV文件来获取测试数据。CSV文件中以井号(#)开头的行被理解为注释,因此会被忽略掉。@CsvFileSource注解的属性解释如下。
resources:指定测试数据文件。
encoding:指定读取文件使用的编码,默认为UTF-8。为了兼容中文,不建议修改该默认编码。
lineSeparator:指定读取文件使用的行分隔符,默认为换行符(\n)。
delimiter:指定分隔字符,默认为英文逗号。
delimiterString:指定分隔字符串,默认为空字符串。
numLinesToSkip:指定需跳过的行数,默认为0。
emptyValue:指定替代测试数据中空字符串的字符串,默认为空字符串。
nullValues:指定应被解释为null值的字符串,默认没有。
首先在/src/test目录下新建一个resources目录,在resources目录中新建testdata.csv文件,文件内容如下。

然后用@CsvFileSource注解替换@CsvSource注解。

执行登录测试方法,两个用户都能登录成功。
lineSeparator属性可修改默认的行分隔符,以下示例将行分隔符修改为英文分号。

显然对应的CSV文件也需要修改,具体如下。

以上CSV文件的换行符使用英文分号进行了替换。
另外,往往CSV文件的第一行会作为“表头”,比如:

此时就需要用到numLinesToSkip属性来跳过第一行。

执行登录测试用例,可观察到CSV文件中的第一行数据未被传递到测试方法中。
@CsvFileSource注解的delimiter、delimiterString、emptyValue和nullValues属性用法与@CsvSource注解的一致,在此不再赘述。
3.2.7 使用@ArgumentsSource和@ArgumentsSources注解
JUnit提供了ArgumentsProvider接口,该接口是配合@ArgumentsSource注解使用的。通过查看源码,可以看到从@ValueSource到@CsvFileSource的所有参数化注解都使用了ArgumentsProvider接口的实现类作为参数提供者。
@ValueSource:使用ValueArgumentsProvider.class
@NullSource:使用NullArgumentsProvider.class
@EmptySource:使用EmptyArgumentsProvider.class
@EnumSource:使用EnumArgumentsProvider.class
@MethodSource:使用MethodArgumentsProvider.class
@CsvSource:使用CsvArgumentsProvider.class
@CsvFileSource:使用CsvFileArgumentsProvider.class
本节介绍直接实现ArgumentsProvider接口,并结合@ArgumentsSource注解来给测试方法提供测试数据。
首先新建一个ArgumentsProviderClass类,该类实现了ArgumentsProvider接口。

ArgumentsProviderClass类重写了ArgumentsProvider接口中的provideArguments(ExtensionContext context)方法,该方法返回一个Stream类型的值,且类型参数需是Arguments或其实现类。
然后用@ArgumentsSource注解替换@CsvFileSource注解。

最后执行登录测试方法,执行结果为通过。
另外还有一个@ArgumentsSources注解,它接收一个@ArgumentsSource类型的数组,使用方式非常简单。
