JUnit 5 - 将多个 CSV 文件参数转换为单个对象
JUnit 5 - Convert multiple CSV file parameters into single object
你好 Java 测试人员。
几天前我开始使用 JUnit 5,因为我喜欢创建参数化测试的新方法。
@ParameterizedTest
允许以参数化方式测试 运行,@CsvFileSource
从 CSV 文件加载参数。
问题是我的 CSV 中有太多列并且不想在我的单元测试中有一个巨大的方法签名。让我举个例子:
@ParameterizedTest
@CsvFileSource(resources = "/test-data.csv")
void myTest(String p1, String p2, String p3, String p4, String p5, String p6) {
//test using parameters
}
我想知道是否有某种转换器可以为我做这样的事情:
@ParameterizedTest
@CsvFileSource(resources = "/test-data.csv")
void myTest(@ConvertWith(TestDataConverter.class) TestData testData) {
//test using parameters
}
static class TestDataConverter implements TestConverter{
public TestData convert(Object ... params){
//a simple method that creates the TestData object and inserts the params in it
}
}
就是这样。
更新答案
从 JUnit Jupiter 5.2 开始:是,有专门的 ArgumentsAggregator
和 ArgumentsAccessor
API 正是用于此目的。
查看 JUnit 用户指南中的 PersonAggregator
示例以获得具体示例。
原答案
从 JUnit Jupiter 5.0 开始:不,目前没有开箱即用的转换器可以为您做到这一点。
原因:JUnit Jupiter 中的参数化测试支持不支持从多个参数到单个参数的映射。因此,正如@Sormuras 所建议的那样,您可以打开一个问题来推荐它。
就执行实际转换而言,您可以考虑使用 uniVocity-parsers,JUnit Jupiter 在内部使用它来解析 CSV 文件。 uniVocity-parsers 还支持直接从 CSV 文件映射到 beans,但要使用它,您需要实现自己的 @TestTemplate
来读取 CSV 文件并执行映射。
其他选项包括来自 Jackson framework or from the JSefa project 的 CSV 支持。
不是通用的解决方案,而是一个有时可行的小技巧:
- 将 CSV 文件的每一行括在引号之间...以便每一行都被视为一个字符串(
sed
、awk
或任何类似的工具都是您的朋友)
现在实现 TestConverter 以便将单个字符串转换为 String[] 以供您随意使用。在许多情况下,它可以像这样简单:
static class TestDataConverter implements TestConverter{
public TestData convert(String string){
String[] params = string.split(",");
//a simple method that creates the TestData object and inserts the params in it
}
}
更新:
一个非常常见的不允许直接使用该技巧的情况是,CSV 中已经使用引号来处理字段内的 'comma':
Name, complex field, City
Carlo, "this field could contain , and other critical chars" , London
Mario, "this field could contain , and other critical chars" , New York
Luca, "this field could contain , and other critical chars" , Milan
用相同的引号引用行显然不起作用。
不过,这种情况或类似情况可以通过对 CSV 文件进行一些额外(但无聊)的转换来解决。
例如假设3个连续的升号“###”可以被认为是一个一致的分隔符:
"Name###complex field###City"
"Carlo###this field could contain , and other critical chars###London"
"Mario###this field could contain , and other critical chars###New York"
"Luca###this field could contain , and other critical chars###Milan"
我知道...这是一个技巧..不是一个明确而优雅的解决方案...:-D
很明显,在这种情况下,拆分将在 3 个哈希值上进行:
String[] params = string.split("###");
您可以使用这个聚合器,尽管有点老套:
public class CsvToMapAggregator implements ArgumentsAggregator {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(CsvToMapAggregator.class)
public @interface CsvToMap {}
private static String[] keys = null; // ATTN: Do not use in parallel runs !!!
@Override public Map<String, Object> aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException {
Object[] values = accessor.toArray();
if (keys == null) {
keys = (String[]) values; // First Line: Header
} else if (values.length != keys.length) {
keys = null; // Last Line: End of Data
return null;
}
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < values.length; i++) {
map.put(keys[i], values[i]); // Read Data from Line
}
return map;
}
}
注意:
所有行必须具有相同数量的项目。
csv 文件必须以额外的行结尾(例如第一列中的"EOF"),该行的数量与前面的行不同。
测试方法将如下所示,跳过 header 和结束行:
@ParameterizedTest(name = "{0}")
@CsvFileSource(resources = "/testcases.csv", delimiter = ';')
@DisplayName("read complete .csv and map columns")
void testCsvData(@CsvToMap Map<String, Object> map, TestInfo info) {
switch (info.getDisplayName()) {
case "Testcase": // First Line: Header
case "EOF": // Last Line: End of File
break;
default:
assertNotNull(map);
assertFalse(map.isEmpty());
}
你好 Java 测试人员。 几天前我开始使用 JUnit 5,因为我喜欢创建参数化测试的新方法。
@ParameterizedTest
允许以参数化方式测试 运行,@CsvFileSource
从 CSV 文件加载参数。
问题是我的 CSV 中有太多列并且不想在我的单元测试中有一个巨大的方法签名。让我举个例子:
@ParameterizedTest
@CsvFileSource(resources = "/test-data.csv")
void myTest(String p1, String p2, String p3, String p4, String p5, String p6) {
//test using parameters
}
我想知道是否有某种转换器可以为我做这样的事情:
@ParameterizedTest
@CsvFileSource(resources = "/test-data.csv")
void myTest(@ConvertWith(TestDataConverter.class) TestData testData) {
//test using parameters
}
static class TestDataConverter implements TestConverter{
public TestData convert(Object ... params){
//a simple method that creates the TestData object and inserts the params in it
}
}
就是这样。
更新答案
从 JUnit Jupiter 5.2 开始:是,有专门的 ArgumentsAggregator
和 ArgumentsAccessor
API 正是用于此目的。
查看 JUnit 用户指南中的 PersonAggregator
示例以获得具体示例。
原答案
从 JUnit Jupiter 5.0 开始:不,目前没有开箱即用的转换器可以为您做到这一点。
原因:JUnit Jupiter 中的参数化测试支持不支持从多个参数到单个参数的映射。因此,正如@Sormuras 所建议的那样,您可以打开一个问题来推荐它。
就执行实际转换而言,您可以考虑使用 uniVocity-parsers,JUnit Jupiter 在内部使用它来解析 CSV 文件。 uniVocity-parsers 还支持直接从 CSV 文件映射到 beans,但要使用它,您需要实现自己的 @TestTemplate
来读取 CSV 文件并执行映射。
其他选项包括来自 Jackson framework or from the JSefa project 的 CSV 支持。
不是通用的解决方案,而是一个有时可行的小技巧:
- 将 CSV 文件的每一行括在引号之间...以便每一行都被视为一个字符串(
sed
、awk
或任何类似的工具都是您的朋友) 现在实现 TestConverter 以便将单个字符串转换为 String[] 以供您随意使用。在许多情况下,它可以像这样简单:
static class TestDataConverter implements TestConverter{ public TestData convert(String string){ String[] params = string.split(","); //a simple method that creates the TestData object and inserts the params in it } }
更新:
一个非常常见的不允许直接使用该技巧的情况是,CSV 中已经使用引号来处理字段内的 'comma':
Name, complex field, City
Carlo, "this field could contain , and other critical chars" , London
Mario, "this field could contain , and other critical chars" , New York
Luca, "this field could contain , and other critical chars" , Milan
用相同的引号引用行显然不起作用。
不过,这种情况或类似情况可以通过对 CSV 文件进行一些额外(但无聊)的转换来解决。
例如假设3个连续的升号“###”可以被认为是一个一致的分隔符:
"Name###complex field###City"
"Carlo###this field could contain , and other critical chars###London"
"Mario###this field could contain , and other critical chars###New York"
"Luca###this field could contain , and other critical chars###Milan"
我知道...这是一个技巧..不是一个明确而优雅的解决方案...:-D
很明显,在这种情况下,拆分将在 3 个哈希值上进行:
String[] params = string.split("###");
您可以使用这个聚合器,尽管有点老套:
public class CsvToMapAggregator implements ArgumentsAggregator {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(CsvToMapAggregator.class)
public @interface CsvToMap {}
private static String[] keys = null; // ATTN: Do not use in parallel runs !!!
@Override public Map<String, Object> aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException {
Object[] values = accessor.toArray();
if (keys == null) {
keys = (String[]) values; // First Line: Header
} else if (values.length != keys.length) {
keys = null; // Last Line: End of Data
return null;
}
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < values.length; i++) {
map.put(keys[i], values[i]); // Read Data from Line
}
return map;
}
}
注意: 所有行必须具有相同数量的项目。
csv 文件必须以额外的行结尾(例如第一列中的"EOF"),该行的数量与前面的行不同。
测试方法将如下所示,跳过 header 和结束行:
@ParameterizedTest(name = "{0}")
@CsvFileSource(resources = "/testcases.csv", delimiter = ';')
@DisplayName("read complete .csv and map columns")
void testCsvData(@CsvToMap Map<String, Object> map, TestInfo info) {
switch (info.getDisplayName()) {
case "Testcase": // First Line: Header
case "EOF": // Last Line: End of File
break;
default:
assertNotNull(map);
assertFalse(map.isEmpty());
}