我写jUnit测试用例有三个目的:
确保我的代码在所有(或大部分)输入组合/值下满足所有所需的功能。
以确保我可以更改实现,并依靠JUnit测试用例来告诉我我的所有功能仍然是满意的。
作为我的代码处理的所有用例的文档,并作为重构的规范——如果代码需要重写的话。(重构代码,如果我的jUnit测试失败——你可能错过了一些用例)。
我不明白为什么或何时应该使用Mockito.verify()。当我看到verify()被调用时,它告诉我我的jUnit正在意识到这个实现。(因此,更改我的实现将破坏我的junit,即使我的功能不受影响)。
我在寻找:
正确使用Mockito.verify()的指导原则是什么?
junit意识到或紧密耦合到被测类的实现,这从根本上是正确的吗?
如果类A的契约包含它调用C类型对象的方法B这一事实,那么您应该通过模拟类型C来测试这一点,并验证方法B已被调用。
这意味着类A的契约有足够的细节来讨论类型C(可能是一个接口或一个类)。所以,是的,我们谈论的是一个超越“系统需求”的规范级别,并且以某种方式描述实现。
这在单元测试中很正常。当您进行单元测试时,您希望确保每个单元都在做“正确的事情”,这通常包括它与其他单元的交互。这里的“单元”可能是指类,或者应用程序的更大的子集。
更新:
我觉得这不仅适用于验证,也适用于存根。一旦您存根了协作者类的方法,您的单元测试就在某种意义上依赖于实现了。这是单元测试的本质。由于Mockito与存根和验证一样重要,使用Mockito的事实意味着您将遇到这种依赖关系。
根据我的经验,如果我改变了一个类的实现,我经常不得不改变它的单元测试的实现来匹配。不过,通常情况下,我不需要更改该类的单元测试清单;当然,除非更改的原因是我之前未能测试的条件的存在。
这就是单元测试的内容。没有这种依赖于协作者类使用方式的测试实际上是子系统测试或集成测试。当然,这些代码也经常是用JUnit编写的,并且经常涉及到mock的使用。在我看来,“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.
我必须说,从经典方法的角度来看,你是绝对正确的:
If you first create (or change) business logic of your application and then cover it with (adopt) tests (Test-Last approach), then it will be very painful and dangerous to let tests know anything about how your software works, other than checking inputs and outputs.
If you are practicing a Test-Driven approach, then your tests are the first to be written, to be changed and to reflect the use cases of your software's functionality. The implementation depends on tests. That sometimes mean, that you want your software to be implemented in some particular way, e.g. rely on some other component's method or even call it a particular amount of times. That is where Mockito.verify() comes in handy!
重要的是要记住,没有通用的工具。软件的类型、规模、公司目标和市场情况、团队技能和许多其他因素都会影响在特定情况下使用哪种方法的决定。
如果类A的契约包含它调用C类型对象的方法B这一事实,那么您应该通过模拟类型C来测试这一点,并验证方法B已被调用。
这意味着类A的契约有足够的细节来讨论类型C(可能是一个接口或一个类)。所以,是的,我们谈论的是一个超越“系统需求”的规范级别,并且以某种方式描述实现。
这在单元测试中很正常。当您进行单元测试时,您希望确保每个单元都在做“正确的事情”,这通常包括它与其他单元的交互。这里的“单元”可能是指类,或者应用程序的更大的子集。
更新:
我觉得这不仅适用于验证,也适用于存根。一旦您存根了协作者类的方法,您的单元测试就在某种意义上依赖于实现了。这是单元测试的本质。由于Mockito与存根和验证一样重要,使用Mockito的事实意味着您将遇到这种依赖关系。
根据我的经验,如果我改变了一个类的实现,我经常不得不改变它的单元测试的实现来匹配。不过,通常情况下,我不需要更改该类的单元测试清单;当然,除非更改的原因是我之前未能测试的条件的存在。
这就是单元测试的内容。没有这种依赖于协作者类使用方式的测试实际上是子系统测试或集成测试。当然,这些代码也经常是用JUnit编写的,并且经常涉及到mock的使用。在我看来,“JUnit”是一个糟糕的名字,因为它可以让我们生成所有不同类型的测试。
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.
David的回答当然是正确的,但并没有很好地解释为什么你想要这样做。
基本上,单元测试是在孤立地测试一个功能单元。您测试输入是否产生预期的输出。有时候,你也必须测试副作用。简而言之,verify允许您这样做。
例如,您有一些业务逻辑,应该使用DAO存储内容。您可以使用集成测试来实现这一点,该测试实例化DAO,将其连接到业务逻辑,然后在数据库中查看是否存储了预期的内容。这不再是单元测试了。
或者,您可以模拟DAO并验证它是否以您期望的方式被调用。使用mockito,您可以验证某个东西是否被调用,它被调用的频率,甚至可以在参数上使用匹配器来确保它以特定的方式被调用。
这样的单元测试的另一面是,你确实将测试与实现捆绑在一起,这使得重构有点困难。另一方面,好的设计味道是正确执行它所需的代码量。如果您的测试需要很长时间,可能是设计出了问题。因此,带有大量副作用/需要测试的复杂交互的代码可能不是一件好事。