【问题标题】:Is there a neater way of testing calls to mocked methods for each item in a list是否有一种更简洁的方法来测试对列表中每个项目的模拟方法的调用
【发布时间】:2010-01-22 02:24:53
【问题描述】:

这是我最近经常遇到的一个模式示例。 我有一个要测试的方法,它需要一个列表,并且可以为列表中的每个项目调用一些其他方法。为了测试这一点,我定义了一个带有预期调用参数的迭代器,并在 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();
    }
}

【问题讨论】:

    标签: java unit-testing jmock jmockit


    【解决方案1】:

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

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

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

    (我省略了均匀度检查,但你明白了)。这类似于不同答案中的 jmockit 示例。请注意,这与您的原始测试不同。特别是它不检查:

    1. doIt 的调用顺序
    2. 参数列表的每个元素只传递一次

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 谢谢,这当然是我想到的,将来会使用它,但我可以在这个项目中使用的测试框架的选择有限。
      • 这个版本实际上并没有测试同样的东西。它仅断言有对bar.doIt()expectedParameters.length() 调用,并且每个调用都将传递expectedParameters 的元素之一。它不会验证每个元素是否只传递一次。如果您的代码包含无法遍历列表的错误,则此测试将找不到它。
      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-02-11
          • 2016-12-25
          • 2011-04-01
          • 2013-07-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多