我写jUnit测试用例有三个目的:

确保我的代码在所有(或大部分)输入组合/值下满足所有所需的功能。 以确保我可以更改实现,并依靠JUnit测试用例来告诉我我的所有功能仍然是满意的。 作为我的代码处理的所有用例的文档,并作为重构的规范——如果代码需要重写的话。(重构代码,如果我的jUnit测试失败——你可能错过了一些用例)。

我不明白为什么或何时应该使用Mockito.verify()。当我看到verify()被调用时,它告诉我我的jUnit正在意识到这个实现。(因此,更改我的实现将破坏我的junit,即使我的功能不受影响)。

我在寻找:

正确使用Mockito.verify()的指导原则是什么? junit意识到或紧密耦合到被测类的实现,这从根本上是正确的吗?


当前回答

正如一些人所说

有时您没有可以断言的直接输出 有时,您只需要确认已测试的方法将正确的间接输出发送给协作者(您正在模拟的协作者)。

Regarding your concern about breaking your tests when refactoring, that is somewhat expected when using mocks/stubs/spies. I mean that by definition and not regarding a specific implementation such as Mockito. But you could think in this way - if you need to do a refactoring that would create major changes on the way your method works, it is a good idea to do it on a TDD approach, meaning you can change your test first to define the new behavior (that will fail the test), and then do the changes and get the test passed again.

其他回答

正如一些人所说

有时您没有可以断言的直接输出 有时,您只需要确认已测试的方法将正确的间接输出发送给协作者(您正在模拟的协作者)。

Regarding your concern about breaking your tests when refactoring, that is somewhat expected when using mocks/stubs/spies. I mean that by definition and not regarding a specific implementation such as Mockito. But you could think in this way - if you need to do a refactoring that would create major changes on the way your method works, it is a good idea to do it on a TDD approach, meaning you can change your test first to define the new behavior (that will fail the test), and then do the changes and get the test passed again.

如果类A的契约包含它调用C类型对象的方法B这一事实,那么您应该通过模拟类型C来测试这一点,并验证方法B已被调用。

这意味着类A的契约有足够的细节来讨论类型C(可能是一个接口或一个类)。所以,是的,我们谈论的是一个超越“系统需求”的规范级别,并且以某种方式描述实现。

这在单元测试中很正常。当您进行单元测试时,您希望确保每个单元都在做“正确的事情”,这通常包括它与其他单元的交互。这里的“单元”可能是指类,或者应用程序的更大的子集。

更新:

我觉得这不仅适用于验证,也适用于存根。一旦您存根了协作者类的方法,您的单元测试就在某种意义上依赖于实现了。这是单元测试的本质。由于Mockito与存根和验证一样重要,使用Mockito的事实意味着您将遇到这种依赖关系。

根据我的经验,如果我改变了一个类的实现,我经常不得不改变它的单元测试的实现来匹配。不过,通常情况下,我不需要更改该类的单元测试清单;当然,除非更改的原因是我之前未能测试的条件的存在。

这就是单元测试的内容。没有这种依赖于协作者类使用方式的测试实际上是子系统测试或集成测试。当然,这些代码也经常是用JUnit编写的,并且经常涉及到mock的使用。在我看来,“JUnit”是一个糟糕的名字,因为它可以让我们生成所有不同类型的测试。

David的回答当然是正确的,但并没有很好地解释为什么你想要这样做。

基本上,单元测试是在孤立地测试一个功能单元。您测试输入是否产生预期的输出。有时候,你也必须测试副作用。简而言之,verify允许您这样做。

例如,您有一些业务逻辑,应该使用DAO存储内容。您可以使用集成测试来实现这一点,该测试实例化DAO,将其连接到业务逻辑,然后在数据库中查看是否存储了预期的内容。这不再是单元测试了。

或者,您可以模拟DAO并验证它是否以您期望的方式被调用。使用mockito,您可以验证某个东西是否被调用,它被调用的频率,甚至可以在参数上使用匹配器来确保它以特定的方式被调用。

这样的单元测试的另一面是,你确实将测试与实现捆绑在一起,这使得重构有点困难。另一方面,好的设计味道是正确执行它所需的代码量。如果您的测试需要很长时间,可能是设计出了问题。因此,带有大量副作用/需要测试的复杂交互的代码可能不是一件好事。

这是个好问题! 我认为其根本原因如下,我们使用JUnit不仅仅是为了单元测试。所以这个问题应该被分解:

我应该在集成测试(或任何其他高于单元测试)中使用Mockito.verify()吗? 我应该在我的黑盒单元测试中使用Mockito.verify()吗? 我应该在白盒单元测试中使用Mockito.verify()吗?

因此,如果我们将忽略高于单元的测试,这个问题可以重新表达为“使用Mockito.verify()使用白盒单元测试在单元测试和我的could实现之间创建了很好的耦合,我可以做一些“灰盒”单元测试,我应该使用什么经验规则”。

现在,让我们一步一步地完成所有这些。

我应该在我的集成(或任何其他高于单元测试)测试中使用Mockito.verify()吗?* 我认为答案显然是否定的,而且你不应该为此使用mock。您的测试应该尽可能接近实际应用。您正在测试完整的用例,而不是应用程序的孤立部分。

*黑盒vs白盒单元测试 如果您正在使用黑盒方法,那么您实际上在做什么呢?您提供(所有等价类)输入、状态和测试,您将收到预期的输出。在这种方法中,使用mock通常是证明(你只是模仿它们正在做正确的事情;你不想测试它们),但是调用Mockito.verify()是多余的。

如果你使用白盒方法,你真正在做的是测试你的单位的行为。在这种方法中,调用Mockito.verify()是必要的,您应该验证您的单元的行为是否如您所期望的那样。

rules of thumbs for grey-box-testing The problem with white-box testing is it creates a high coupling. One possible solution is to do grey-box-testing, not white-box-testing. This is sort of combination of black&white box testing. You are really testing the behaviour of your unit like in white-box testing, but in general you make it implementation-agnostic when possible. When it is possible, you will just make a check like in black-box case, just asserts that output is what is your expected to be. So, the essence of your question is when it is possible.

This is really hard. I don't have a good example, but I can give you to examples. In the case that was mentioned above with equals() vs equalsIgnoreCase() you shouldn't call Mockito.verify(), just assert the output. If you couldn't do it, break down your code to the smaller unit, until you can do it. On the other hand, suppose you have some @Service and you are writting @Web-Service that is essentially wrapper upon your @Service - it delegates all calls to the @Service (and making some extra error handling). In this case calling to Mockito.verify() is essential, you shouldn't duplicate all of your checks that you did for the @Serive, verifying that you're calling to @Service with correct parammeter list is sufficient.

In most cases when people don't like using Mockito.verify, it is because it is used to verify everything that the tested unit is doing and that means you will need to adapt your test if anything changes in it. But, I don't think that is a problem. If you want to be able to change what a method does without the need to change it's test, that basically means you want to write tests which don't test everything your method is doing, because you don't want it to test your changes. And that is the wrong way of thinking.

真正的问题是,如果你可以修改你的方法所做的事情,那么一个应该完全覆盖功能的单元测试就不会失败。这意味着无论更改的意图是什么,更改的结果都不会被测试覆盖。

Because of that, I prefer to mock as much as possible: also mock your data objects. When doing that you can not only use verify to check that the correct methods of other classes are called, but also that the data being passed is collected via the correct methods of those data objects. And to make it complete, you should test the order in which calls occur. Example: if you modify a db entity object and then save it using a repository, it is not enough to verify that the setters of the object are called with the correct data and that the save method of the repository is called. If they are called in the wrong order, your method still doesn't do what it should do. So, I don't use Mockito.verify but I create an inOrder object with all mocks and use inOrder.verify instead. And if you want to make it complete, you should also call Mockito.verifyNoMoreInteractions at the end and pass it all the mocks. Otherwise someone can add new functionality/behavior without testing it, which would mean after while your coverage statistics can be 100% and still you are piling up code which isn't asserted or verified.