验证 void 方法调用

verify void method call

我有以下 classes:

public class A {
    private Field field;
    public A(Field field){
        this.field = field;
    }
    public add(int size){
        field.addBall(new Ball(size));
    }
}

public interface Things {
    List<Ball> ballList = new LinkedList<>();
    public addBall(Ball b){
        ballList.add(b);
    }
}

我想测试 A class 的 add() 方法。更具体地说,我想测试 class Things 的 addBall() 方法是否被调用。 此测试失败说:验证时预期失败:Things.addBall(...):预期:1,实际:0;

public class TestA {
        private Things thing;
        private A a;
        @Before
        public void setUp() {
            thing = EasyMock.createNiceMock(Things.class);
            a = new A(thing);
        }

        @After
        public void tearDown() {
        }
    @Test
        public void addTest(){
            thing.addBall(new Ball(345));
            EasyMock.expectLastCall();
            EasyMock.replay(cache);
            a.add(345);
        EasyMock.verify(cache);
        }
}

什么是正确的做法?这个测试有什么问题?

我通常使用 Mockito,但这是我的猜测:

当 EasyMock 将您传递到记录阶段的 new Ball(345) 的结果与 a.add(345)new Ball(size) 的结果进行比较时,您正在查看两个完全不同的 Ball对象。他们不比较equal(),尽管大小相同

作为一般规则,对 new 的显式调用在您测试时会很麻烦。一种可能的解决方案是引入 BallFactory。然后你可以验证 factory.createBall(345) 被调用,它的结果,一个 Ball 的模拟是传递给 addBall()

(这里有一篇关于这个主题的优秀文章:http://misko.hevery.com/2008/07/08/how-to-think-about-the-new-operator/

如果你宁愿放宽你的测试要求,只检查 addBall() 是否被任何对象调用(我不建议这样做),你可以使用 EasyMock 的 anyObject() 方法来匹配任意 Ball.

我假设名为 cache 的变量实际上是 thing。那么,另一个答案是正确的。默认情况下,参数使用 equals 匹配器。您可能还没有在 Ball.

上定义 equals

我做了不同的实现来展示如何在不添加 equals 方法的情况下解决这个问题。

关于代码的一些评论:

  • 这里不需要漂亮的模拟。一个普通的模拟实际上更安全
  • 我喜欢静态导入我的测试代码:-)
  • expectLastCall 没有必要。默认为 void 方法
  • 我修复了很多编译错误。下次尝试给出工作示例

首先是固定码:

public class Things {
  List<Ball> ballList = new LinkedList<>();

  public void addBall(Ball b){
    ballList.add(b);
  }
}

public class A {
  private Things field;

  public A(Things field){
    this.field = field;
  }

  public void add(int size){
    field.addBall(new Ball(size));
  }
}

我在 Ball 中添加了 getSize 以提供实际示例。

public class Ball {

  private final int size;

  public Ball(int size) {
    this.size = size;
  }

  public int getSize() {
    return size;
  }
}

最后,TestA

public class TestA {
  private Things thing;
  private A a;

  @Before
  public void setUp() {
    thing = createMock(Things.class);
    a = new A(thing);
  }

  @Test
  public void addTest(){
      // You should keep only one of these examples
      // 1. You don't care about checking `345`. You just want to make sure there is a call. `anyObject` is exactly what you need
     thing.addBall(anyObject());
     // 2. You want to expect a `Ball` with `345` 
     thing.addBall(cmp(new Ball(345), Comparator.comparingInt(Ball::getSize), LogicalOperator.EQUAL));
    // 3. You prefer a simpler check possibly on many attributes. Just capture the `Ball` passed in parameter and check after if it contains what you want
    Capture<Ball> capture = Capture.newInstance();
    thing.addBall(capture(capture));
    // Now the original code
    replay(thing);
    a.add(345);
    verify(thing);
    // Assert on the capture is you used solution #3
    assertEquals(345, capture.getValue().getSize());
  }
}