我正在搜索如何测试是否已正确地从数据库中删除某些内容,但我找到了以下答案:https://stackoverflow.com/a/38082803/9115438,但让我开始思考,如果delete方法失败并且实际上并未删除该对象,然后怎样呢? verify方法仅检查delete方法是否被调用一次或被调用一次且是否成功? 因为如果不检查删除是否成功,则该测试根本没有用。
答案 0 :(得分:2)
您的观点很重要。
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格式描述/复制您方法的实现。
它没有根据返回结果声明任何内容。以错误的方式更改实现(添加错误的调用或删除所需的调用)很难通过测试失败来检测,因为测试只是反映了所调用的语句。
通常要谨慎使用模拟验证。
在某些特定情况下,这可能会有所帮助,但在许多情况下,您只需要维护这些无助的代码,并且由于种种原因使构建速度变慢。
实际上,对于实质上依赖于具有副作用的功能的测试方法,应该支持集成测试(甚至是切片/部分测试)。
Mocks Aren't Stubs of Martin Fowler是您应该感兴趣的优秀文章。
当我观察到一个嘲笑者时,这尤其让我震惊 程序员。我真的很喜欢在编写测试时 关注行为的结果,而不是行为的方式。一个嘲笑者是 不断思考如何在以下环境中实现SUT 为了写出期望。这对我来说真的很不自然。
答案 1 :(得分:2)
您指的是Junit: writing a test for a method that deletes an entity?中的示例:
public void deleteFromPerson(person person) {
person = personRepository.returnPerson(person.getId());
personRepository.delete(person);
}
是的,是的,在单元测试中模拟delete
方法时,您只会知道是否调用了delete
,而不知道删除是否成功。 delete
无法成功的两种可能性是什么?
a)delete
的调用不正确,可能缺少某些参数,或者需要做一些准备来删除。在单元测试中找不到这样的问题:如果您关于如何调用另一个组件的假设是错误的,并且您对另一个组件进行了模拟,那么实现模拟的方式将仅反映您自己的误解,并且进行单元测试将会成功。但是,这并不是单元测试的缺陷:这就是为什么除了单元测试之外,还存在诸如集成测试之类的其他级别的原因,这些级别旨在发现那些单元测试无法找到的错误。集成测试实际上旨在发现组件之间交互中的错误。
b)delete
有时可能会在运行时失败,无论您是否正确调用delete
方法。例如,您的代码可能没有对personRepository
的写权限,或者同时有一些并行线程删除了此人。但是,示例代码没有采取任何措施来应对这种运行时场景(嗯,它只是一部分示例代码,但也可能有意地像这样,请参见davidxxx的评论)。但是,我们假设应该有一些代码来处理不成功的delete
。
正确地进行模拟时(即通过查看delete
的规范),那么在单元测试期间就已经很明显delete
可能会失败。在这种情况下,可能会返回错误代码或引发异常。开发人员在意识到这一点时,可以决定通过相应的错误处理代码来扩展上述示例代码。而且,可以通过模拟delete
来对错误处理代码进行单元测试,从而也可以执行这些错误方案。
相反,如果开发人员没有意识到delete
可能会失败,那么,我们首先会有一个集成问题:开发人员的单元测试将在{{1} }永不失败。而且,在使用(不完全)模拟的delete
进行单元测试期间,不会发现这种误解。同样,在集成测试期间必须遇到delete
在运行时可能失败的情况。然后,再次必须扩展示例代码,将扩展单元测试,等等。
编辑:以上内容旨在说明单元测试和集成测试之间的关系以及模拟的作用。 davidxxx正确指出的是,对于这段示例代码,单元测试将没有太大的价值:该代码本质上是由交互组成的-单元测试无法捕获错误。因此,对于此示例代码,测试应立即从集成测试开始。