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

时间:2009-06-26 14:24:40

标签: java unit-testing jmock jmockit

这是我最近遇到的模式的一个例子。 我有一个测试方法,它接受一个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();
    }
}

4 个答案:

答案 0 :(得分:1)

也许以下(使用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);
   }
}

答案 1 :(得分:1)

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

但是,如果您正在寻找一种方法来预期对方法进行特定数量的调用,则可以使用exactly(n).of()

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

(我省略了均匀度检查,但你明白了)。这与另一个答案中的jmockit示例类似。请注意,这不会测试与原始测试相同的内容。特别是它没有检查:

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

答案 2 :(得分:0)

您可以简化此测试。你知道自己想要什么,所以你可以更加具体地了解代码:

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()调用的需要。

答案 3 :(得分:0)

值得记住的是,您不必一次创造所有期望。您可以在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,这也有助于提高清晰度。