是否有一个更简洁的方式来测试列表中每个项目的模拟方法调用

问题描述:

这是我最近遇到很多模式的一个例子。 我有一个需要测试的方法需要一个List,并且可以为列表中的每个项目调用一些其他方法。为了测试这个,我定义了一个Iterator,它带有期望的调用参数和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(); 
    } 
} 

我认为你目前的测试实现非常接近理想。任何进一步的压缩都可能会改变测试的语义或者将测试的意图模糊到读者(或两者兼而有之)。

但是,如果你正在寻找一种方式,以期望调用的方法的具体数量,你可以使用exactly(n).of()

mockery.checking(new Expectations() {{ 
    exactly(expectedParameters.length()).of(bar).doIt(with(anyOf(expectedParameters))); 
}}); 

(我离开了条干均匀度检查,但你的想法)。这与jmockit示例中的不同答案类似。请注意,这不会测试与原始测试相同的内容。特别是它不检查:

  1. 的呼叫到doIt
  2. ,参数列表中的每个元素恰好经过一次

例如订单,这个测试会通过,如果你的方法按相反顺序在列表中迭代,或者它只是调用doIt方法n次,但每次都传递列表的第一个元素。如果你想确保列表中的每个元素都被传递,你几乎必须遍历它,为每个元素设置一个单独的期望值。如果您不关心调用的顺序,则可以省略序列的使用(在这种情况下,您可能希望将原始方法更改为接受Collection而不是List)。

也许以下(使用JMockit而不是jMock)?


import java.util.*; 

import org.junit.*; 
import org.junit.runner.*; 

import org.hamcrest.*; 
import static org.hamcrest.core.AnyOf.*; 
import static org.hamcrest.core.Is.*; 
import org.hamcrest.core.*; 

import mockit.*; 
import mockit.integration.junit4.*; 

@RunWith(JMockit.class) 
public class FooTest 
{ 
    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 void setBar(final Bar bar) { this.bar = bar; } 
    } 

    @Test 
    public void testExecuteEven(final Bar bar) 
    { 
     final List<String> allParameters = new ArrayList<String>(); 
     final List<Matcher<? extends String>> expectedParameters = 
     new ArrayList<Matcher<? extends String>>(); 

     for (int i = 0; i < 3; i++) { 
     allParameters.add("param" + i); 

     if (i % 2 == 0) { 
      expectedParameters.add(new IsEqual<String>("param" + i)); 
     } 
     } 

     new Expectations() 
     { 
     { 
      bar.doIt(with(anyOf(expectedParameters))); repeats(expectedParameters.size()); 
     } 
     }; 

     Foo subject = new Foo(); 
     subject.setBar(bar); 
     subject.executeEven(allParameters); 
    } 

    @Test // a shorter version of the same test 
    public void testExecuteEven2(final Bar bar) 
    { 
     final List<String> allParameters = Arrays.asList("param0", "param1", "param2"); 

     new Expectations() 
     { 
     { 
      bar.doIt(with(anyOf(is("param0"), is("param2")))); repeats(2); 
     } 
     }; 

     Foo subject = new Foo(); 
     subject.setBar(bar); 
     subject.executeEven(allParameters); 
    } 
} 
+0

谢谢,这确实是我想到的事情,将来会用到它,但是我对这个项目可以使用的测试框架的选择有限。 – 2009-06-27 21:45:46

您可以简化此测试。你知道你想要什么,所以你可以更具体的有关代码:

public void testExecuteEven() { 
    final List<String> values = Arrays.asList("param0", "param1", "param2", "param3"); 
    Sequence evens = mockery.sequence("evens"); 

    mockery.checking(new Expectations() {{ 
    oneOf(bar).doIt(values.get(0)); inSequence(evens); 
    oneOf(bar).doIt(values.get(2)); inSequence(evens); 
    }}); 

    subject.executeEven(values); 
} 

如果你正在使用JUnit 4,不要忘了,在类的@RunWith(JMock.class)注释避免需要assertIsSatisfied()调用。

值得一提的是,您不必一次创建您的期望。您可以在checking(new Expectations(){{}})区块之外执行循环,并在最终将其传递给嘲弄之前操作期望列表。这可以清晰帮助在复杂的期望值设置(所以不会评论!):

@Test 
public void testExecuteEven() { 

    Mockery mockery = new Mockery(); 
    Sequence evens = mockery.sequence("evens"); 
    final Bar bar = mockery.mock(Bar.class); 

    List<Expectations> expectations = new ArrayList<Expectations>(); 

    final List<String> allParameters = new ArrayList<String>(); 
    final List<String> expectedParameters = new ArrayList<String>(); 


    // generate some parameters 
    for (int i = 0; i < 3; i++) { 
     allParameters.add("param" + i); 
     if (i % 2 == 0) { 
     expectedParameters.add("param" + i); 
     } 
    } 

    // define expectations for the expected parameters 
    for (String param : expectedParameters) { 
    expectations.add(new Expectations() {{ oneOf(bar).doIt(param); inSequence(evens); }}); 
    } 

    // define any special expectations here 
    expectations.add(new Expectations() {{ oneOf(bar).doSomethingElse() /* whatever */ }}); 

    // load the expectations into the mockery 
    for (Expectations expectation : expectations) { 
    mockery.checking(expectation); 
    } 

    Foo subject = new Foo(); 
    subject.setBar(bar); 
    subject.executeEven(allParameters); 

} 

另外,我你不使用Java 5的foreach语句通知。如果你没有使用Java 4,也可以帮助清晰起见。