【发布时间】:2017-05-30 10:11:26
【问题描述】:
我为我正在使用的界面创建了一个自定义 Hamcrest 匹配器。
匹配器是TypeSafeMatcher 的一个实例,它覆盖了以下三个方法:
-
TypeSafeMatcher#matchesSafely(T item):boolean -
TypeSafeMatcher#describeMismatchSafely(T item, Description mismatchDescription):void -
TypeSafeMatcher#describeTo(Description description):void
我匹配的类处理特定类型对象的验证。它来自外部库,所以我不能简单地更改它。我们称这个类为ValidationSubject
ValidationSubject 这个类的每个实例都定义了要执行的验证背后的一些逻辑。这是通过实现ValidationSubject#validate(ValidationData validationData) 来完成的,其中validationData 是一个构建器类型的对象,它允许程序员根据实现ValidationSubject 的类的对象的状态来报告验证错误
public class Foo implements ValidationSubject {
private String state;
private Map<String, Baz> moreState;
// constructor, methods affecting the state
// this method is required by ValidationSubject
@Override
public void validate(ValidationData validationData) {
/*
* call methods on validationData based on the state
* of the object
*/
}
}
我正在使用我的匹配器来测试每个具体类中实现的验证逻辑,例如Foo。
为了做到这一点,我需要在每个测试用例中存根/模拟/监视ValidationData 的一个实例,并查看ValidationData 对象的状态如何根据主体在下面执行的逻辑发生变化测试。这是很多样板。我希望我的匹配器将其抽象出来
assertThat(testedValidationSubject, hasValidationErrors("Illegal character in name", "Description exceeds 200 words", "Age cannot be negative"));
在这种情况下,我真正与hasValidationErrors 匹配器的参数匹配的是一组字符串值,被测对象存储在ValidationData 对象中。
提取这些值需要一些代码。
return new TypeSafeMatcher<ValidationSubject>() {
@Override
protected boolean matchesSafely(ValidationSubject item) {
// this calls the relevant methods on 'item' internally
Validator validator = new Validator(item);
List<ValidationMessage> errorMessages = validator.getErrorMessageGroup()
.getMessages();
Set<String> actualMessages = errorMessages.stream().map(e -> e.getMessage())
.collect(Collectors.toSet());
Set<String> expectedMessages = Stream.of(expectedErrors).collect(Collectors.toSet());
Set<String> missingMessages = SetUtils.difference(expectedMessages, actualMessages);
Set<String> unexpectedMessages = SetUtils.difference(actualMessages, expectedMessages);
return SetUtils.union(unexpectedMessages, missingMessages).isEmpty();
}
@Override
public void describeMismatchSafely(final ValidationSubject item, final Description description) {
// this calls the relevant methods on 'item' internally
Validator validator = new Validator(item);
List<ValidationMessage> errorMessages = validator.getErrorMessageGroup()
.getMessages();
Set<String> actualMessages = errorMessages.stream().map(e -> e.getMessage())
.collect(Collectors.toSet());
Set<String> expectedMessages = Stream.of(expectedErrors).collect(Collectors.toSet());
Set<String> missingMessages = SetUtils.difference(expectedMessages, actualMessages);
Set<String> unexpectedMessages = SetUtils.difference(actualMessages, expectedMessages);
description.appendText("Validation errors were missing or unexpected\n")
.appendValueList("\tSupefluous messages: ", ", ", "\n", unexpectedMessages.toArray())
.appendValueList("\tMissing messages: ", ", ", "\n", missingMessages.toArray());
}
@Override
public void describeTo(Description description) {
description.appendText("validation should result in the expected errors");
}
}
这段代码逐行重复:
Validator validator = new Validator(item);
List<ValidationMessage> errorMessages = validator.getErrorMessageGroup()
.getMessages();
Set<String> actualMessages = errorMessages.stream().map(e -> e.getMessage())
.collect(Collectors.toSet());
Set<String> expectedMessages = Stream.of(expectedErrors).collect(Collectors.toSet());
Set<String> missingMessages = SetUtils.difference(expectedMessages, actualMessages);
Set<String> unexpectedMessages = SetUtils.difference(actualMessages, expectedMessages);
我可以通过将此片段包装在方法或 lambda 表达式中(返回一对集合或接受一个函数作为参数来计算我需要的布尔值或字符串)来消除重复,但理想情况下,我'希望只执行一次。
我需要item 来计算matchesSafely 和describemisMatchSafely 输出的消息的结果,但每次都作为参数传递。它不是静态方法hasValidationErrors 的参数,所以我看不到将结果缓存在几个变量中的干净方法。
我可能会在其中一个方法中执行此代码并将其缓存在一个字段中,但the Javadoc for TypeSafeMatcher 似乎无法保证首先执行哪个方法。
【问题讨论】: