Mockito 验证方法如何工作?

Ain*_*ugh 5 java testing unit-testing mocking mockito

我正在搜索如何测试某些内容是否已从数据库中正确删除,我找到了这个答案:/sf/answers/2665796241/但这让我思考,如果删除方法失败并且没有失败怎么办实际上删除了该对象,然后呢?verify 方法是否只检查删除方法是否被调用过一次,或者是否被调用过一次并且是否成功?因为如果它不检查删除是否成功,那么该测试根本没有用。

dav*_*xxx 5

你的观点是相关的。
Mockito.verify()将仅验证在方法执行期间是否在模拟上完成了调用。
这意味着如果模拟类的真正实现没有按预期工作,则测试是无助的。
因此,您在测试中模拟的类/方法也必须进行统一测试。
但这是否意味着verify()一切都好呢?并不真地。

假设一个测试删除某些内容的方法:

public class Foo{
    MyDao myDao;

    public void delete(int id){
       myDao.delete(id);
    }
}
Run Code Online (Sandbox Code Playgroud)

测试通过:

@Mock
MyDao myDaoMock;
Foo foo;

@Test
public void delete(int id){
   foo.delete(id);
   Mockito.verify(myDaoMock).delete(id);
}
Run Code Online (Sandbox Code Playgroud)

假设现在我将实现更改为:

public void delete(int id){
   myDao.delete(id);
   myDao.create(id);
}
Run Code Online (Sandbox Code Playgroud)

测试仍然是绿色的......哎呀。

其他场景,假设一个主要调用依赖方法的方法:

public void doThat(){
   Foo foo = fooDep.doThat(...);
   Bar bar = barDep.doThat(foo);
   FooBar fooBar = fooBarDep.doThat(foo, bar);
   fooBis.doOtherThing(...);
   // and so for
}
Run Code Online (Sandbox Code Playgroud)

通过验证方法,单元测试将仅以 Mockito 格式描述/复制方法的实现。
它对返回结果没有任何断言。以错误的方式更改实现(添加不正确的调用或删除所需的调用)很难通过测试失败来检测,就像测试只是调用语句的反映一样。

模拟验证通常需要谨慎使用。
在某些特定情况下,它可能会有所帮助,但在许多情况下,我看到开发人员滥用它(75%或更多的单元测试类是模拟设置/记录/验证),因此它产生了一个膨胀的单元测试价值不大,很难维护,而且还会因为不公平的原因减慢你的构建速度。
事实上,对于基本上依赖于具有副作用的函数的测试方法,集成测试(甚至切片/部分)应该受到青睐。


Mocks Aren’t Stubs of Martin Fowler是一篇优秀的文章,您应该会感兴趣。

当我观察一个模拟程序员时,这一点给我留下了特别深刻的印象。我真的很喜欢这样一个事实:在编写测试时,您关注的是行为的结果,而不是行为的完成方式。模拟者不断思考 SUT 将如何实现,以便写出期望。这对我来说真的很不自然。

虽然 Martin Fowler 的这篇文章很有趣,但我也不同意所有观点。
我同意采用模拟方法,开发人员不会模拟依赖项,因为依赖项很烦人,但系统地这样做通常是一个坏主意
我们应该总是有充分的理由来引入模拟,例如:

  • 外部系统依赖
  • 调用缓慢
  • 再现性问题
  • 与依赖项交互的复杂设置(输入和/或输出)

但我不同意显式创建存根通常是一个好主意,因为存根代码需要时间编写,可能有错误,我们必须维护它。最后,为了使事情变得干净健壮,存根类也应该进行统一测试。所有这一切都是有代价的。
另一方面,模拟库生成的模拟没有所有这些缺陷。
没有什么可以阻止我们以存根的方式使用这些模拟:使模拟作为存根进行协作,即:

  • 是,用于输入/输出记录
  • 是的,对于少数人来说是相关的verify()
  • 但不得verify()滥用
  • 并且没有将测试与实现相结合的细粒度模拟行为记录的乘法