是否有一种更简洁的方法来测试对列表中每个项目的模拟方法的调用
这是我最近经常遇到的模式的一个例子。 我有一个要测试的方法,它接受一个列表,并可能为列表中的每个项目调用一些其他方法。 为了测试这一点,我定义了一个具有预期调用参数的迭代器,并在 JMock 期望中定义了一个循环,以检查是否针对迭代器的每个项目进行了调用(请参见下面的简单示例)。
我看过 Hamcrest 匹配器,但没有找到对此进行测试的东西(或者误解了可用匹配器的工作原理)。 有人有更优雅的方法吗?
package com.hsbc.maven.versionupdater;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.Sequence;
import org.jmock.internal.NamedSequence;
public class FooTest extends AbstractMojoTestCase {
public interface Bar {
void doIt(String arg);
}
public class Foo {
private Bar bar;
public void executeEven(final List<String> allParameters) {
for (int i = 0; i < allParameters.size(); i++) {
if (i % 2 == 0) {
bar.doIt(allParameters.get(i));
}
}
}
public Bar getBar() {
return bar;
}
public void setBar(final Bar bar) {
this.bar = bar;
}
}
public void testExecuteEven() {
Mockery mockery = new Mockery();
final Bar bar = mockery.mock(Bar.class);
final Sequence sequence = new NamedSequence("sequence");
final List<String> allParameters = new ArrayList<String>();
final List<String> expectedParameters = new ArrayList<String>();
for (int i = 0; i < 3; i++) {
allParameters.add("param" + i);
if (i % 2 == 0) {
expectedParameters.add("param" + i);
}
}
final Iterator<String> iter = expectedParameters.iterator();
mockery.checking(new Expectations() {
{
while (iter.hasNext()) {
one(bar).doIt(iter.next());
inSequence(sequence);
}
}
});
Foo subject = new Foo();
subject.setBar(bar);
subject.executeEven(allParameters);
mockery.assertIsSatisfied();
}
}
This is an example of a pattern I've encountered a lot recently.
I have a method to be tested that takes a List and may invoke some other method(s) for each item in the list. To test this I define an Iterator with the expected call parameters and a loop in the JMock expectations to check the call is made against each item of the iterator (see trivial example below).
I've had a look at the Hamcrest matchers but haven't found something that tests for this (or have misunderstood how the available matchers work). Does anyone have a more elegant approach?
package com.hsbc.maven.versionupdater;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.Sequence;
import org.jmock.internal.NamedSequence;
public class FooTest extends AbstractMojoTestCase {
public interface Bar {
void doIt(String arg);
}
public class Foo {
private Bar bar;
public void executeEven(final List<String> allParameters) {
for (int i = 0; i < allParameters.size(); i++) {
if (i % 2 == 0) {
bar.doIt(allParameters.get(i));
}
}
}
public Bar getBar() {
return bar;
}
public void setBar(final Bar bar) {
this.bar = bar;
}
}
public void testExecuteEven() {
Mockery mockery = new Mockery();
final Bar bar = mockery.mock(Bar.class);
final Sequence sequence = new NamedSequence("sequence");
final List<String> allParameters = new ArrayList<String>();
final List<String> expectedParameters = new ArrayList<String>();
for (int i = 0; i < 3; i++) {
allParameters.add("param" + i);
if (i % 2 == 0) {
expectedParameters.add("param" + i);
}
}
final Iterator<String> iter = expectedParameters.iterator();
mockery.checking(new Expectations() {
{
while (iter.hasNext()) {
one(bar).doIt(iter.next());
inSequence(sequence);
}
}
});
Foo subject = new Foo();
subject.setBar(bar);
subject.executeEven(allParameters);
mockery.assertIsSatisfied();
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我认为您当前的测试实现非常接近理想。 任何进一步的压缩都有可能改变测试的语义或使读者模糊测试的意图(或两者兼而有之)。
但是,如果您正在寻找一种方法来预期对方法的特定调用次数,则可以使用
exactly(n).of()
:(我省略了均匀性检查,但您明白了)。 这与不同答案中的 jmockit 示例类似。 请注意,这不会测试与原始测试相同的内容。 特别是,它不会检查:
doIt
的顺序例如,如果您的方法以相反的顺序迭代列表,则此测试将通过,或者,如果它只是调用
doIt
方法n
次,但每次都传递了列表的第一个元素。 如果您想确保列表中的每个元素都被传递,您几乎必须对其进行迭代,为每个元素设置单独的期望。 如果您不关心调用的顺序,则可以省略 Sequence 的使用(在这种情况下,您可能需要更改原始方法以接受 Collection 而不是 List)。I think your current test implementation is pretty close to ideal. Any further compaction risks either changing the semantics of the test or obscuring the intent of the test to a reader (or both).
However, if you're looking for a way to expect a specific number of calls to a method, you can use
exactly(n).of()
:(I left out the evenness check, but you get the idea). This is similar to the jmockit example in a different answer. Be aware that this does not test the same thing as your original test. In particular it does not check:
doIt
For example, this test would pass if your method iterated over the list in reverse order, or if it just called the
doIt
methodn
times but passed the first element of the list each time. If you want to ensure that each element in the list is passed, you pretty much have to iterate over it setting an individual expectation for each. If you don't care about the order of the invocations, you can omit the use of the Sequence (in that case you may want to change your original method to accept a Collection instead of a List).也许以下(使用 JMockit 而不是 jMock)?
Perhaps the following (using JMockit instead of jMock)?
您可以简化此测试。 您知道自己想要什么,因此可以更具体地了解代码:
如果您使用 JUnit 4,请不要忘记类上的 @RunWith(JMock.class) 注释避免了对 assertIsSatisfied() 调用的需要。
You can simplify this test. You know what you want, so you can be more concrete about the code:
If you're using JUnit 4, don't forget that the @RunWith(JMock.class) annotation on the class avoids the need for the assertIsSatisfied() call.
值得记住的是,您不必一次性创建所有期望。 您可以在
checking(new Expectations(){{}})
块之外执行循环,并在最终将其传递给模拟之前操作期望列表。 这可以帮助澄清复杂的期望设置(注释也是如此!):另外,我注意到您没有使用 Java 5 foreach 语句。 如果您不纠结于使用 Java 4,这也可以帮助您更加清晰。
It's worth remembering that you don't have to create your expectations all at once. You can do your loop outside of the
checking(new Expectations(){{}})
block and manipulate the expectations list before finally passing it in to the mockery. This can help with clarity in complicated expectation setups (and so does commenting!):Also, I notice you're not using Java 5 foreach statements. If you're not stuck using Java 4 that can also help with clarity.