以更智能的方式在 Junit 5(或其他测试 Java 库)中使用数组进行参数化
Parameterizing with array in Junit 5 (or other testing Java library) in smarter fashion
我正在尝试参数化此测试:
@Test
public void reverseQuote(double[] qsp) throws Exception {
...}
对我来说似乎很荒谬,它不存在一些初始化数组 qsp
的快速方法,例如 ValueSource
:
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
assertNotNull(argument);
}
我的目标是做类似 @ValueSource(doublesArray = {new double[]{1.0, 2.0, 3.0}})
的事情(现在 returns 错误)。不存在任何允许类似的东西吗?
似乎只建议详细的方法,比如使用 @MethodSource
或 @ConvertWith
。
我也接受实现其他测试库的答案。
JUnit 文档建议使用 @MethodSource
@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(double argument) {
assertNotEquals(9.0, argument);
}
static DoubleStream range() {
return DoubleStream.range(0.0, 20.0);
}
否则你可以考虑使用这个:
https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/params/provider/ValueSource.html#doubles
@ParameterizedTest
@ValueSource(doubles = { 1, 2, 3 })
public void test(double numberToTest){
//do whatever
}
你可以查看TestNg(我在我的项目中使用它)。在我的项目中,您可以在此处查看 enter link description here 或下方的示例:
@DataProvider(name = "processText")
public Object[][] dataProvider() {
return new Object[][]{
new Object[]{"ala\nma\nkota", "grep ma", "ma"},
new Object[]{"ala\nma\nkota", "grep -v ma", "ala\nkota"},
new Object[]{"ala\nma\nkota", "cut -c1-3", "ala\nma\nkot"},
//...
new Object[]{"ala ma kota", "sed s/ma/XX/g", "ala XX kota"},
new Object[]{"ala\nma\nkota", "grep -v ma | sed s/a/G/g", "GlG\nkotG"},
};
}
@Test(dataProvider = "processText")
public void testProcessText(String text, String cli, String expected) {
final String actual = new UnixProcessing().processText(text, cli);
assertEquals(actual, expected);
}
官方 TestNg 文档是 here
可能需要考虑结合使用 Junit 参数化测试和 YAML 解析。
@RunWith(Parameterized.class)
public class AnotherParameterizedTest {
private final HashMap row;
@Parameterized.Parameters(name="Reverse Lists Tests # {index}:")
public static List<Map<String, Object>> data() {
final TestData testData = new TestData(""+
"| ID | List | Expected | \n"+
"| 0 | [1, 2, 3] | [3, 2, 1] | \n"+
"| 1 | [2, 3, 5] | [3, 2, 1] | \n"+
"| 2 | [5, 6, 7] | [ 7, 6, 5] | \n"
);
// parsing each row using simple YAML parser and create map per row
return testData.getDataTable();
}
// Each row from the stringified table above will be
// split into key=value pairs where the value are parsed using a
// yaml parser. this way, values can be pretty much any yaml type
// like a list of integers in this case.
public AnotherParameterizedTest(HashMap obj) {
this.row = obj;
}
@Test
public void test() throws Exception {
List orgListReversed = new ArrayList((List) row.get("List"));
Collections.reverse(orgListReversed);
assertEquals((List) row.get("Expected"), orgListReversed);
}
}
我没有使用字符串,而是使用 Excel Reader 来使用简单的方法执行相同的操作
Excel 表。使用 YAML 作为值将每一行解析为一个映射。
Junit IDE Test Results
刚刚使用 Junit Jupiter 进行的测试在 IDE Runner 中给出了更好的结果。
import static org.junit.jupiter.api.Assertions.assertEquals;
import de.deicon.yatf.runner.dsl.TestData;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class FirstTest {
@ParameterizedTest
@MethodSource("testTable")
public void test(Map row){
List reversedList = (List) row.get("List");
Collections.reverse(reversedList);
assertEquals((List)row.get("Expected"), reversedList);
}
static List<Map<String, Object>> testTable() {
return new TestData(""+
"|ID| List |Expected | \n"+
"|0 | [1,2,3] | [3,2,1] | \n"+
"|1 | [hans, peter, klaus] | [klaus, peter, hans] | \n"
).getDataTable();
}
}
我喜欢使用 Spock 来测试 Java 代码。它是一个基于 groovy 的测试框架,位于 JUnit 4 之上。Spock 中的参数化测试是一个内置功能:
def "The reverseQuote method doesn't return null"(double[] qsp) {
when: "reverseQuote is called"
double[] rev = reverseQuote(qsp)
then: "the result is not null"
null != rev
where: "there are various input values"
qsp << [
[0.1, 0.2, 0.3] as double[],
[1.0, 2.0, 3.0] as double[]
]
}
...或者,您可以以表格形式列出测试数据:
def "The reverseQuote method reverses the input array"(List qsp, List expected) {
when: "reverseQuote is called"
double[] rev = reverseQuote(qsp as double[])
then: "the result is the reverse of the input"
expected as double[] == rev
where: "there are various input values"
qsp | expected
[0.1, 0.2, 0.3] | [0.3, 0.2, 0.1]
[1.0, 2.0, 3.0] | [3.0, 2.0, 1.0]
}
注意 as double[]
流行是 Groovy 自动将数组转换为列表的不幸结果,因此我们必须在我们正在与实际需要数组的 Java 代码进行交互的特殊情况。
好吧,这将是一个奇怪的答案,但它有效,而且这样做很有趣。
第一件事:你的方法是不可能的。不是因为 JUnit 或任何相关的 API,而是因为 Java - valid annotation type elements(注解参数只能是原始类型、字符串、Class、枚举、其他注解和数组所有这些)。
第二件事:我们可以绕过第一件事。检查这个:
@ArraySources(
arrays = {
@ArraySource(array = {1, 2, 3}),
@ArraySource(array = {4, 5, 6}),
@ArraySource(array = {7, 8, 9})
}
)
正如它所说,注释可以将其他注释作为参数,以及这些注释的数组,所以我们在这里使用这 2 条规则。
第三件事:这有什么帮助?我们可以添加自己的注释 + 参数提供程序,JUnit 5 可以通过这种方式扩展。
两个注释:
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ArgumentsSource(ArrayArgumentsProvider.class)
public @interface ArraySources {
ArraySource[] arrays();
}
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ArraySource {
int[] array() default {};
}
基于注释的参数提供者:
public class ArrayArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<ArraySources> {
private List<int[]> arguments;
public void accept(ArraySources source) {
List<ArraySource> arrays = Arrays.asList(source.arrays());
this.arguments = arrays.stream().map(ArraySource::array).collect(Collectors.toList());
}
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return this.arguments.stream().map(Arguments::of);
}
}
最终测试使用的是:
public class ArraySourcesTest {
@ParameterizedTest
@ArraySources(
arrays = {
@ArraySource(array = {1, 2, 3}),
@ArraySource(array = {4, 5, 6}),
@ArraySource(array = {7, 8, 9})
}
)
void example(int[] array) {
System.out.println(Arrays.toString(array));
System.out.println("Test Over");
}
}
/* Output
[1, 2, 3]
Test Over
[4, 5, 6]
Test Over
[7, 8, 9]
Test Over
*/
你提到 @MethodSource
很复杂,嗯,所以我想我在这件事上失败了,但它有效。它可以明显地简化和增强(比如将注释参数命名为默认值 - 值 - 而我这样做只是为了 int
来展示这个想法)。不确定您是否可以使用现有功能(ArgumentsProvider
和 ArgumentSources
)实现相同的功能,但这看起来更具体(您知道您正在使用数组)并显示扩展 JUnit5 的可能性,可能对其他情况。
数组:
static Stream<Arguments> yourTest () {
return Stream.of(
Arguments.of((new int[] { 2, 1, 2, 3, 4 }), 3),
Arguments.of((new int[] { 2, 2, 0 }), 3),
Arguments.of((new int[]{1, 3, 5} ) ,0 )
);
}
// if not Array : List : Arguments.of((Arrays.asList(0, 1) ), 0.5)...
@ParameterizedTest(name = "{index} => array = {0} ), expected = {1} ")
@MethodSource("yourTest")
void shoultCountEvens( int[] array, int expected) {
assertEquals( CountEvens.countEvens(array), expected );
}
public class CountEvens {
public static int countEvens(int[] nums) {
long count = Arrays.stream(nums)
.filter(a -> a % 2 == 0)
.count();
return Math.toIntExact(count);
}}
我正在尝试参数化此测试:
@Test
public void reverseQuote(double[] qsp) throws Exception {
...}
对我来说似乎很荒谬,它不存在一些初始化数组 qsp
的快速方法,例如 ValueSource
:
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
assertNotNull(argument);
}
我的目标是做类似 @ValueSource(doublesArray = {new double[]{1.0, 2.0, 3.0}})
的事情(现在 returns 错误)。不存在任何允许类似的东西吗?
@MethodSource
或 @ConvertWith
。
我也接受实现其他测试库的答案。
JUnit 文档建议使用 @MethodSource
@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(double argument) {
assertNotEquals(9.0, argument);
}
static DoubleStream range() {
return DoubleStream.range(0.0, 20.0);
}
否则你可以考虑使用这个: https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/params/provider/ValueSource.html#doubles
@ParameterizedTest
@ValueSource(doubles = { 1, 2, 3 })
public void test(double numberToTest){
//do whatever
}
你可以查看TestNg(我在我的项目中使用它)。在我的项目中,您可以在此处查看 enter link description here 或下方的示例:
@DataProvider(name = "processText")
public Object[][] dataProvider() {
return new Object[][]{
new Object[]{"ala\nma\nkota", "grep ma", "ma"},
new Object[]{"ala\nma\nkota", "grep -v ma", "ala\nkota"},
new Object[]{"ala\nma\nkota", "cut -c1-3", "ala\nma\nkot"},
//...
new Object[]{"ala ma kota", "sed s/ma/XX/g", "ala XX kota"},
new Object[]{"ala\nma\nkota", "grep -v ma | sed s/a/G/g", "GlG\nkotG"},
};
}
@Test(dataProvider = "processText")
public void testProcessText(String text, String cli, String expected) {
final String actual = new UnixProcessing().processText(text, cli);
assertEquals(actual, expected);
}
官方 TestNg 文档是 here
可能需要考虑结合使用 Junit 参数化测试和 YAML 解析。
@RunWith(Parameterized.class)
public class AnotherParameterizedTest {
private final HashMap row;
@Parameterized.Parameters(name="Reverse Lists Tests # {index}:")
public static List<Map<String, Object>> data() {
final TestData testData = new TestData(""+
"| ID | List | Expected | \n"+
"| 0 | [1, 2, 3] | [3, 2, 1] | \n"+
"| 1 | [2, 3, 5] | [3, 2, 1] | \n"+
"| 2 | [5, 6, 7] | [ 7, 6, 5] | \n"
);
// parsing each row using simple YAML parser and create map per row
return testData.getDataTable();
}
// Each row from the stringified table above will be
// split into key=value pairs where the value are parsed using a
// yaml parser. this way, values can be pretty much any yaml type
// like a list of integers in this case.
public AnotherParameterizedTest(HashMap obj) {
this.row = obj;
}
@Test
public void test() throws Exception {
List orgListReversed = new ArrayList((List) row.get("List"));
Collections.reverse(orgListReversed);
assertEquals((List) row.get("Expected"), orgListReversed);
}
}
我没有使用字符串,而是使用 Excel Reader 来使用简单的方法执行相同的操作 Excel 表。使用 YAML 作为值将每一行解析为一个映射。
Junit IDE Test Results
刚刚使用 Junit Jupiter 进行的测试在 IDE Runner 中给出了更好的结果。
import static org.junit.jupiter.api.Assertions.assertEquals;
import de.deicon.yatf.runner.dsl.TestData;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class FirstTest {
@ParameterizedTest
@MethodSource("testTable")
public void test(Map row){
List reversedList = (List) row.get("List");
Collections.reverse(reversedList);
assertEquals((List)row.get("Expected"), reversedList);
}
static List<Map<String, Object>> testTable() {
return new TestData(""+
"|ID| List |Expected | \n"+
"|0 | [1,2,3] | [3,2,1] | \n"+
"|1 | [hans, peter, klaus] | [klaus, peter, hans] | \n"
).getDataTable();
}
}
我喜欢使用 Spock 来测试 Java 代码。它是一个基于 groovy 的测试框架,位于 JUnit 4 之上。Spock 中的参数化测试是一个内置功能:
def "The reverseQuote method doesn't return null"(double[] qsp) {
when: "reverseQuote is called"
double[] rev = reverseQuote(qsp)
then: "the result is not null"
null != rev
where: "there are various input values"
qsp << [
[0.1, 0.2, 0.3] as double[],
[1.0, 2.0, 3.0] as double[]
]
}
...或者,您可以以表格形式列出测试数据:
def "The reverseQuote method reverses the input array"(List qsp, List expected) {
when: "reverseQuote is called"
double[] rev = reverseQuote(qsp as double[])
then: "the result is the reverse of the input"
expected as double[] == rev
where: "there are various input values"
qsp | expected
[0.1, 0.2, 0.3] | [0.3, 0.2, 0.1]
[1.0, 2.0, 3.0] | [3.0, 2.0, 1.0]
}
注意 as double[]
流行是 Groovy 自动将数组转换为列表的不幸结果,因此我们必须在我们正在与实际需要数组的 Java 代码进行交互的特殊情况。
好吧,这将是一个奇怪的答案,但它有效,而且这样做很有趣。
第一件事:你的方法是不可能的。不是因为 JUnit 或任何相关的 API,而是因为 Java - valid annotation type elements(注解参数只能是原始类型、字符串、Class、枚举、其他注解和数组所有这些)。
第二件事:我们可以绕过第一件事。检查这个:
@ArraySources(
arrays = {
@ArraySource(array = {1, 2, 3}),
@ArraySource(array = {4, 5, 6}),
@ArraySource(array = {7, 8, 9})
}
)
正如它所说,注释可以将其他注释作为参数,以及这些注释的数组,所以我们在这里使用这 2 条规则。
第三件事:这有什么帮助?我们可以添加自己的注释 + 参数提供程序,JUnit 5 可以通过这种方式扩展。
两个注释:
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ArgumentsSource(ArrayArgumentsProvider.class)
public @interface ArraySources {
ArraySource[] arrays();
}
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ArraySource {
int[] array() default {};
}
基于注释的参数提供者:
public class ArrayArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<ArraySources> {
private List<int[]> arguments;
public void accept(ArraySources source) {
List<ArraySource> arrays = Arrays.asList(source.arrays());
this.arguments = arrays.stream().map(ArraySource::array).collect(Collectors.toList());
}
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return this.arguments.stream().map(Arguments::of);
}
}
最终测试使用的是:
public class ArraySourcesTest {
@ParameterizedTest
@ArraySources(
arrays = {
@ArraySource(array = {1, 2, 3}),
@ArraySource(array = {4, 5, 6}),
@ArraySource(array = {7, 8, 9})
}
)
void example(int[] array) {
System.out.println(Arrays.toString(array));
System.out.println("Test Over");
}
}
/* Output
[1, 2, 3]
Test Over
[4, 5, 6]
Test Over
[7, 8, 9]
Test Over
*/
你提到 @MethodSource
很复杂,嗯,所以我想我在这件事上失败了,但它有效。它可以明显地简化和增强(比如将注释参数命名为默认值 - 值 - 而我这样做只是为了 int
来展示这个想法)。不确定您是否可以使用现有功能(ArgumentsProvider
和 ArgumentSources
)实现相同的功能,但这看起来更具体(您知道您正在使用数组)并显示扩展 JUnit5 的可能性,可能对其他情况。
数组:
static Stream<Arguments> yourTest () {
return Stream.of(
Arguments.of((new int[] { 2, 1, 2, 3, 4 }), 3),
Arguments.of((new int[] { 2, 2, 0 }), 3),
Arguments.of((new int[]{1, 3, 5} ) ,0 )
);
}
// if not Array : List : Arguments.of((Arrays.asList(0, 1) ), 0.5)...
@ParameterizedTest(name = "{index} => array = {0} ), expected = {1} ")
@MethodSource("yourTest")
void shoultCountEvens( int[] array, int expected) {
assertEquals( CountEvens.countEvens(array), expected );
}
public class CountEvens {
public static int countEvens(int[] nums) {
long count = Arrays.stream(nums)
.filter(a -> a % 2 == 0)
.count();
return Math.toIntExact(count);
}}