EasyMock:如何在集合顺序无关紧要的情况下验证值集合的方法顺序
EasyMock: How to Verify Method Order for Set of Values Where Order of Set Does Not Matter
我有一个测试,其中我有一组特定值,两个不同的方法将针对该组中的每个值执行一次。我需要检查这两种方法是否以相对于彼此的特定顺序调用,但与值集的顺序无关。例如:
String[] values = { "A", "B", "C" };
for (...<loop over values...) {
methodOne(value);
methodTwo(value);
}
values
的顺序无关紧要,但我需要验证是否为集合中的每个值调用了 methodOne()
和 methodTwo()
并且 methodOne()
总是在 methodTwo()
.
之前调用
我知道我可以创建一个控件并为每个值期望 methodOne()
和 methodTwo()
,然后执行 control.verify()
,但这取决于 values
在具体顺序。
有没有优雅的方法来做到这一点?
谢谢
您可以使用 andAnswer()
来做到这一点。
基本上,在 methodOne()
的 andAnswer()
中,您设置了一些变量来保存传入 value
的内容。
然后在 methodTwo()
的 andAnswer()
中,您断言相同的参数与您从 methodOne 答案中保存的内容相匹配。
由于每次调用 methodOne
都会修改此变量,因此会确保 methodTwo() 始终在 methodOne() 之后调用。
注意这个解决方案不是线程安全的
首先,您需要一些东西来保存 methodOne 调用中的变量。这可以是一个简单的 class,具有单个字段,甚至是一个元素的数组。您需要此包装器对象,因为您需要在需要最终或有效最终字段的 IAnswer 中引用它。
private class CurrentValue{
private String methodOneArg;
}
现在您的期望。这里我调用了你正在测试的 class (The System Under Test) sut
:
String[] values = new String[]{"A", "B", "C"};
final CurrentValue currentValue = new CurrentValue();
sut.methodOne(isA(String.class));
expectLastCall().andAnswer(new IAnswer<Void>() {
@Override
public Void answer() throws Throwable {
//save the parameter passed in to our holder object
currentValue.methodOneArg =(String) EasyMock.getCurrentArguments()[0];
return null;
}
}).times(values.length); // do this once for every element in values
sut.methodTwo(isA(String.class));
expectLastCall().andAnswer(new IAnswer<Void>() {
@Override
public Void answer() throws Throwable {
String value =(String) EasyMock.getCurrentArguments()[0];
//check to make sure the parameter matches the
//the most recent call to methodOne()
assertEquals(currentValue.methodOneArg, value);
return null;
}
}).times(values.length); // do this once for every element in values
replay(sut);
... //do your test
verify(sut);
编辑
你是对的,如果你使用 EasyMock 2.4 +,你可以使用新的 Capture
class 以更清晰的方式为 methodOne()
获取参数值。但是,您可能仍需要为 methodTwo()
使用 andAnswer()
以确保按顺序调用正确的值。
这是使用 Capture 的相同代码
Capture<String> captureArg = new Capture<>();
sut.methodOne(and(capture(captureArg), isA(String.class)));
expectLastCall().times(values.length);
sut.methodTwo(isA(String.class));
expectLastCall().andAnswer(new IAnswer<Void>() {
@Override
public Void answer() throws Throwable {
String value =(String) EasyMock.getCurrentArguments()[0];
assertEquals(captureArg.getValue(), value);
return null;
}
}).times(values.length);
replay(sut);
对于那些感兴趣的人,我使用预期的 EasyMock 功能解决了这个问题。解决方案是制作一个自定义 IArgumentMatcher 来验证一组值并强制每个值连续匹配的次数。自定义匹配器,除了使用严格的模拟之外,还完全解决了原来的问题。
public class SetMatcher implements IArgumentMatcher {
private List<String> valuesToMatch;
private List<String> remainingValues;
private String currentValue = null;
private int timesMatched = 0;
private int setMatches;
public SetMatcher(final List<String> valuesToMatch, final int times) {
this.valuesToMatch = new ArrayList<String>(valuesToMatch);
this.remainingValues = new ArrayList<String>(valuesToMatch);
this.setMatches = times;
}
public String use() {
EasyMock.reportMatcher(this);
return null;
}
public void appendTo(StringBuffer buffer) {
if (this.remainingValues.size() == 0) {
buffer.append("all values in " + this.valuesToMatch + " already matched " + this.setMatches + " time(s)");
} else {
buffer.append("match " + this.valuesToMatch + " " + this.setMatches + " time(s) each");
}
}
public boolean matches(Object other) {
if (this.timesMatched >= this.setMatches) {
this.currentValue = null;
this.timesMatched = 0;
}
if (null == this.currentValue) {
if (this.remainingValues.contains(other)) {
this.currentValue = (String) other;
this.timesMatched = 1;
this.remainingValues.remove(other);
return true;
}
} else if (this.currentValue.equals(other)) {
this.timesMatched++;
return true;
}
return false;
}
}
正在测试的class:
public class DataProcessor {
private ServiceOne serviceOne;
private ServiceTwo serviceTwo;
public DataProcessor(ServiceOne serviceOne, ServiceTwo serviceTwo) {
this.serviceOne = serviceOne;
this.serviceTwo = serviceTwo;
}
public void processAll(List<String> allValues) {
List<String> copy = new ArrayList<String>(allValues);
for (String value : copy) {
this.serviceOne.preProcessData(value);
this.serviceTwo.completeTransaction(value);
}
}
}
测试:
public class DataProcessorTest {
List<String> TEST_VALUES = Arrays.asList("One", "Two", "Three", "Four", "Five");
@Test
public void test() {
IMocksControl control = EasyMock.createStrictControl();
ServiceOne serviceOne = control.createMock(ServiceOne.class);
ServiceTwo serviceTwo = control.createMock(ServiceTwo.class);
SetMatcher matcher = new SetMatcher(TEST_VALUES, 2);
for (int i = 0; i < TEST_VALUES.size(); i++) {
serviceOne.preProcessData(matcher.use());
serviceTwo.completeTransaction(matcher.use());
}
control.replay();
DataProcessor dataProcessor = new DataProcessor(serviceOne, serviceTwo);
dataProcessor.processAll(TEST_VALUES);
control.verify();
}
}
测试将因以下任何一项而失败:
- ServiceOne 和 ServiceTwo 的调用顺序错误
- ServiceOne 和 ServiceTwo 没有连续调用相同的值
- 使用不在指定值列表中的值调用了 ServiceOne 或 ServiceTwo
- 对列表中的某个值进行了超出预期次数的调用
我有一个测试,其中我有一组特定值,两个不同的方法将针对该组中的每个值执行一次。我需要检查这两种方法是否以相对于彼此的特定顺序调用,但与值集的顺序无关。例如:
String[] values = { "A", "B", "C" };
for (...<loop over values...) {
methodOne(value);
methodTwo(value);
}
values
的顺序无关紧要,但我需要验证是否为集合中的每个值调用了 methodOne()
和 methodTwo()
并且 methodOne()
总是在 methodTwo()
.
我知道我可以创建一个控件并为每个值期望 methodOne()
和 methodTwo()
,然后执行 control.verify()
,但这取决于 values
在具体顺序。
有没有优雅的方法来做到这一点?
谢谢
您可以使用 andAnswer()
来做到这一点。
基本上,在 methodOne()
的 andAnswer()
中,您设置了一些变量来保存传入 value
的内容。
然后在 methodTwo()
的 andAnswer()
中,您断言相同的参数与您从 methodOne 答案中保存的内容相匹配。
由于每次调用 methodOne
都会修改此变量,因此会确保 methodTwo() 始终在 methodOne() 之后调用。
注意这个解决方案不是线程安全的
首先,您需要一些东西来保存 methodOne 调用中的变量。这可以是一个简单的 class,具有单个字段,甚至是一个元素的数组。您需要此包装器对象,因为您需要在需要最终或有效最终字段的 IAnswer 中引用它。
private class CurrentValue{
private String methodOneArg;
}
现在您的期望。这里我调用了你正在测试的 class (The System Under Test) sut
:
String[] values = new String[]{"A", "B", "C"};
final CurrentValue currentValue = new CurrentValue();
sut.methodOne(isA(String.class));
expectLastCall().andAnswer(new IAnswer<Void>() {
@Override
public Void answer() throws Throwable {
//save the parameter passed in to our holder object
currentValue.methodOneArg =(String) EasyMock.getCurrentArguments()[0];
return null;
}
}).times(values.length); // do this once for every element in values
sut.methodTwo(isA(String.class));
expectLastCall().andAnswer(new IAnswer<Void>() {
@Override
public Void answer() throws Throwable {
String value =(String) EasyMock.getCurrentArguments()[0];
//check to make sure the parameter matches the
//the most recent call to methodOne()
assertEquals(currentValue.methodOneArg, value);
return null;
}
}).times(values.length); // do this once for every element in values
replay(sut);
... //do your test
verify(sut);
编辑
你是对的,如果你使用 EasyMock 2.4 +,你可以使用新的 Capture
class 以更清晰的方式为 methodOne()
获取参数值。但是,您可能仍需要为 methodTwo()
使用 andAnswer()
以确保按顺序调用正确的值。
这是使用 Capture 的相同代码
Capture<String> captureArg = new Capture<>();
sut.methodOne(and(capture(captureArg), isA(String.class)));
expectLastCall().times(values.length);
sut.methodTwo(isA(String.class));
expectLastCall().andAnswer(new IAnswer<Void>() {
@Override
public Void answer() throws Throwable {
String value =(String) EasyMock.getCurrentArguments()[0];
assertEquals(captureArg.getValue(), value);
return null;
}
}).times(values.length);
replay(sut);
对于那些感兴趣的人,我使用预期的 EasyMock 功能解决了这个问题。解决方案是制作一个自定义 IArgumentMatcher 来验证一组值并强制每个值连续匹配的次数。自定义匹配器,除了使用严格的模拟之外,还完全解决了原来的问题。
public class SetMatcher implements IArgumentMatcher {
private List<String> valuesToMatch;
private List<String> remainingValues;
private String currentValue = null;
private int timesMatched = 0;
private int setMatches;
public SetMatcher(final List<String> valuesToMatch, final int times) {
this.valuesToMatch = new ArrayList<String>(valuesToMatch);
this.remainingValues = new ArrayList<String>(valuesToMatch);
this.setMatches = times;
}
public String use() {
EasyMock.reportMatcher(this);
return null;
}
public void appendTo(StringBuffer buffer) {
if (this.remainingValues.size() == 0) {
buffer.append("all values in " + this.valuesToMatch + " already matched " + this.setMatches + " time(s)");
} else {
buffer.append("match " + this.valuesToMatch + " " + this.setMatches + " time(s) each");
}
}
public boolean matches(Object other) {
if (this.timesMatched >= this.setMatches) {
this.currentValue = null;
this.timesMatched = 0;
}
if (null == this.currentValue) {
if (this.remainingValues.contains(other)) {
this.currentValue = (String) other;
this.timesMatched = 1;
this.remainingValues.remove(other);
return true;
}
} else if (this.currentValue.equals(other)) {
this.timesMatched++;
return true;
}
return false;
}
}
正在测试的class:
public class DataProcessor {
private ServiceOne serviceOne;
private ServiceTwo serviceTwo;
public DataProcessor(ServiceOne serviceOne, ServiceTwo serviceTwo) {
this.serviceOne = serviceOne;
this.serviceTwo = serviceTwo;
}
public void processAll(List<String> allValues) {
List<String> copy = new ArrayList<String>(allValues);
for (String value : copy) {
this.serviceOne.preProcessData(value);
this.serviceTwo.completeTransaction(value);
}
}
}
测试:
public class DataProcessorTest {
List<String> TEST_VALUES = Arrays.asList("One", "Two", "Three", "Four", "Five");
@Test
public void test() {
IMocksControl control = EasyMock.createStrictControl();
ServiceOne serviceOne = control.createMock(ServiceOne.class);
ServiceTwo serviceTwo = control.createMock(ServiceTwo.class);
SetMatcher matcher = new SetMatcher(TEST_VALUES, 2);
for (int i = 0; i < TEST_VALUES.size(); i++) {
serviceOne.preProcessData(matcher.use());
serviceTwo.completeTransaction(matcher.use());
}
control.replay();
DataProcessor dataProcessor = new DataProcessor(serviceOne, serviceTwo);
dataProcessor.processAll(TEST_VALUES);
control.verify();
}
}
测试将因以下任何一项而失败:
- ServiceOne 和 ServiceTwo 的调用顺序错误
- ServiceOne 和 ServiceTwo 没有连续调用相同的值
- 使用不在指定值列表中的值调用了 ServiceOne 或 ServiceTwo
- 对列表中的某个值进行了超出预期次数的调用