在Cucumber中解码DataTable时如何正确上报自定义异常?
问题场景
使用Cucumber 7.18和Java 21时,希望测试场景的错误消息足够直观,方便测试工程师快速定位问题,但DataTable转换过程中自定义异常消息会丢失,仅显示通用转换失败提示。
例如测试场景中第三行CODE列存在非法非整数值:
Given My system produces the following entries: | NAME | ORC | CODE | EPT | EFL | TIME | | AFR89YB | A | 4455 | DEP | 100 | -00:10 | | AFR89YC | B | 4455 | DEP | 100 | -00:10 | | AFR89YD | C | Illegal value | DEP | 100 | -00:10 | | ... | ... | ... | ... | ... | ... |
通过@DataTableType注解注册转换方法:
@DataTableType public MySystemEntry mySystemEntry(Map<String, String> entry) { return MySystemEntry.builder() .entry(entry) .build(); }
在MySystemEntry的Builder中添加验证规则,非法值时抛出自定义IllegalArgumentException:
public class MySystemEntry { // ... public static Builder builder() { return new Builder(); } public static class Builder { // ... public Builder entry(Map<String, String> entry) { // ... String value = entry.get("CODE"); if (notANumber(value)) { throw new IllegalArgumentException("Value " + value + " is illegal for Column 'CODE'."); } // ... return this; } // ... } }
但实际运行时,自定义消息丢失,日志仅显示:
Step failed io.cucumber.datatable.CucumberDataTableException: 'java.util.List<com.my-application.MySystemEntry>' could not transform | NAME | ORC | CODE | EPT | EFL | TIME | | AFR89YB | A | 4455 | DEP | 100 | -00:10 | | AFR89YC | B | 4455 | DEP | 100 | -00:10 | | AFR89YD | C | Illegal value | DEP | 100 | -00:10 | | ... | ... | ... | ... | ... | ... | at io.cucumber.datatable.DataTableType.transform(DataTableType.java:158) ... at ✽.Given My system produces the following entries: (file:///D:/dev/java/my-application/src/test/resources/features/my-system-works.feature:26) Caused by: io.cucumber.core.backend.CucumberInvocationTargetException at io.cucumber.java.Invoker.doInvoke(Invoker.java:73) ...
测试工程师无法快速定位错误,需逐一排查数据。
解决方案
要保留自定义错误消息,需调整异常抛出的时机与方式:
1. 在@DataTableType方法中捕获并重新抛出异常
在注册的转换方法里,捕获Builder抛出的异常,补充行信息后重新抛出,确保自定义消息被保留:
@DataTableType public MySystemEntry mySystemEntry(Map<String, String> entry) { try { return MySystemEntry.builder() .entry(entry) .build(); } catch (IllegalArgumentException e) { String rowInfo = String.format("Row with NAME '%s' has invalid data: ", entry.get("NAME")); throw new IllegalArgumentException(rowInfo + e.getMessage(), e); } }
2. 实现TableTransformer接口做精细控制
若需要更精准的行号提示,可实现TableTransformer接口,在遍历行时处理异常:
public class MySystemEntryTableTransformer implements TableTransformer<List<MySystemEntry>> { @Override public List<MySystemEntry> transform(DataTable table) throws CucumberException { List<Map<String, String>> rows = table.asMaps(); List<MySystemEntry> entries = new ArrayList<>(); for (int i = 0; i < rows.size(); i++) { Map<String, String> row = rows.get(i); try { entries.add(MySystemEntry.builder().entry(row).build()); } catch (IllegalArgumentException e) { throw new CucumberDataTableException( String.format("Error in row %d: %s", i + 2, e.getMessage()), // 行号+2是因为表头占一行 e ); } } return entries; } }
然后在步骤定义类中注册该转换器:
@DefaultParameterTransformer @DefaultDataTableEntryTransformer @DefaultDataTableCellTransformer public Object transformer(Object fromValue, Type toValueType) { Configuration configuration = new Configuration(); return configuration.getTransformerRegistry().getTransformer(toValueType).transform(fromValue); } @DataTableType public TableTransformer<List<MySystemEntry>> mySystemEntryTableTransformer() { return new MySystemEntryTableTransformer(); }
关键原理
Cucumber转换DataTable时,会将底层异常包装为CucumberInvocationTargetException,最终转为CucumberDataTableException。直接在@DataTableType方法中抛出带明确消息的异常,或捕获后重新抛出时保留原始消息,Cucumber会将这些消息包含在最终输出中,让测试工程师直接看到错误原因与位置。
验证方式
调整代码后运行测试,日志将显示包含自定义消息的错误提示,例如:Error in row 4: Value Illegal value is illegal for Column 'CODE'.
内容的提问来源于stack exchange,提问作者jmgonet

