Java Mockito验证方法是如何工作的?
我在研究如何测试某个东西是否从数据库中被正确删除,我发现了这个答案:但这让我思考,如果delete方法失败,并且它实际上没有删除对象,那该怎么办? verify方法是否只检查delete方法是否调用过一次,或者是否调用过一次并且成功?Java Mockito验证方法是如何工作的?,java,unit-testing,testing,mocking,mockito,Java,Unit Testing,Testing,Mocking,Mockito,我在研究如何测试某个东西是否从数据库中被正确删除,我发现了这个答案:但这让我思考,如果delete方法失败,并且它实际上没有删除对象,那该怎么办? verify方法是否只检查delete方法是否调用过一次,或者是否调用过一次并且成功? 因为如果它没有检查删除是否成功,那么该测试就毫无用处。你的观点是相关的。 Mockito.verify()将只验证在方法执行期间是否对模拟进行了调用。 这意味着,如果模拟类的实际实现不能正常工作,测试将无能为力。 因此,您在测试中模拟的类/方法也必须进行单一测试。
因为如果它没有检查删除是否成功,那么该测试就毫无用处。你的观点是相关的。
Mockito.verify()
将只验证在方法执行期间是否对模拟进行了调用。这意味着,如果模拟类的实际实现不能正常工作,测试将无能为力。
因此,您在测试中模拟的类/方法也必须进行单一测试。
但这是否意味着
verify()
总是很好?不是真的
假设要测试的方法删除了某些内容:
public class Foo{
MyDao myDao;
public void delete(int id){
myDao.delete(id);
}
}
测试通过:
@Mock
MyDao myDaoMock;
Foo foo;
@Test
public void delete(int id){
foo.delete(id);
Mockito.verify(myDaoMock).delete(id);
}
假设现在我将实现更改为:
public void delete(int id){
myDao.delete(id);
myDao.create(id);
}
测试仍然是绿色的。。。哎呀
其他场景,假设一个方法主要调用依赖项方法:
public void doThat(){
Foo foo = fooDep.doThat(...);
Bar bar = barDep.doThat(foo);
FooBar fooBar = fooBarDep.doThat(foo, bar);
fooBis.doOtherThing(...);
// and so for
}
使用验证方法,单元测试将以Mockito格式描述/复制方法的实现。它对返回的结果不作任何断言。以错误的方式更改实现(添加不正确的调用或删除所需的调用)很难检测到测试失败,就像测试只是调用语句的反映一样 模拟验证通常需要谨慎使用。
在某些特定情况下,这可能会有所帮助,但在许多情况下,我看到开发人员滥用了这一点(75%或更多的单元测试类是模拟设置/记录/验证),因此它产生了一个膨胀的单元测试,几乎没有价值,很难维护,并且由于不公平的原因也会减慢构建速度。
事实上,对于基本上依赖于具有副作用的函数的测试方法,应该支持集成测试(甚至是切片/部分测试)
这是一篇你应该感兴趣的好文章 当我观察到一个嘲弄者时,我尤其感到震惊 程序员我真的很喜欢这样的事实,在写测试的时候 关注行为的结果,而不是它是如何完成的。嘲弄者是 不断思考SUT将如何在 为了写下期望。我觉得这很不自然 虽然这篇马丁·福勒的文章很有趣,但我也不完全同意。
我同意mockist方法,即开发人员不模拟依赖项,因为依赖项令人讨厌,但系统地模拟依赖项,这通常是一个坏主意
我们应该始终有充分的理由引入模拟,例如:
- 外部系统依赖性
- 调用缓慢
- 再现性问题
- 与依赖项(输入和/或输出)交互的复杂设置
另一方面,由模拟库生成的模拟没有所有这些缺陷。
没有什么能阻止我们以存根思维使用这些模拟:让模拟作为存根进行协作,即:
- 输入/输出记录为是
- 对于少数相关的
verify()
- 但不适用于
滥用verify()
- 对于将测试与实现耦合的细粒度模拟行为记录的乘法,则没有
delete
方法时,您只会判断是否调用了delete
,而不会判断删除是否成功。delete
失败的两种可能性是什么
a) delete
调用不正确,可能缺少某个参数,或者需要为删除做准备。这样的问题在单元测试中是找不到的:如果您关于如何调用另一个组件的假设是错误的,并且您模拟了另一个组件,那么您实现模拟的方式只会反映您自己的误解,单元测试就会成功。然而,这并不是单元测试的一个缺陷:这就是为什么除了单元测试之外,还存在其他级别(如集成测试)的原因,集成测试旨在发现单元测试无法发现的缺陷。集成测试实际上是为了在组件之间的交互中发现bug
b) delete
有时可能在运行时失败,无论您是否正确调用了delete
方法。例如,您的代码可能没有对personRepository
的写访问权限,或者某个并行线程同时删除了此人或其他内容。但是,示例代码没有任何措施来处理这样的运行时场景(好吧,它只是一段示例代码,但也可能是故意这样,请参阅davidxxx的注释)。但是,我们假设应该有一些代码来处理未成功的delete
如果正确地进行模拟(即通过查看delete
的规范),那么在单元测试期间,delete
可能会失败。在这种情况下,可能会返回错误代码或引发异常。开发人员在意识到这一点时,可以决定通过相应的错误处理代码扩展上述示例代码。而且,可以通过模拟delete
对错误处理代码进行单元测试,这样这些错误场景也可以
public void deleteFromPerson(person person) {
person = personRepository.returnPerson(person.getId());
personRepository.delete(person);
}