简体   繁体   English

什么时候使用 Mockito.verify()?

[英]When to use Mockito.verify()?

I write jUnit test cases for 3 purposes:我为 3 个目的编写 jUnit 测试用例:

  1. To ensure that my code satisfies all of the required functionality, under all (or most of) the input combinations/values.为了确保我的代码满足所有(或大部分)输入组合/值下的所有必需功能。
  2. To ensure that I can change the implementation, and rely on JUnit test cases to tell me that all my functionality is still satisfied.确保我可以更改实现,并依靠 JUnit 测试用例告诉我我的所有功能仍然满足。
  3. As a documentation of all the use cases my code handles, and act as a spec for refactoring - should the code ever need to be rewritten.作为我的代码处理的所有用例的文档,并作为重构的规范 - 如果代码需要重写。 (Refactor the code, and if my jUnit tests fail - you probably missed some use case). (重构代码,如果我的 jUnit 测试失败 - 您可能错过了一些用例)。

I do not understand why or when Mockito.verify() should be used.我不明白为什么或何时应该使用Mockito.verify() When I see verify() being called, it is telling me that my jUnit is becoming aware of the implementation.当我看到调用verify() ,它告诉我我的 jUnit 正在意识到实现。 (Thus changing my implementation would break my jUnits, even though my functionality was unaffected). (因此更改我的实现会破坏我的 jUnit,即使我的功能不受影响)。

I'm looking for:我在找:

  1. What should be the guidelines for appropriate usage of Mockito.verify() ?适当使用Mockito.verify()的准则应该是什么?

  2. Is it fundamentally correct for jUnits to be aware of, or tightly coupled to, the implementation of the class under test? jUnits 知道或紧密耦合到被测类的实现是否从根本上是正确的?

If the contract of class A includes the fact that it calls method B of an object of type C, then you should test this by making a mock of type C, and verifying that method B has been called.如果类 A 的契约包括它调用类型 C 对象的方法 B 的事实,那么您应该通过创建类型 C 的模拟来测试这一点,并验证方法 B 已被调用。

This implies that the contract of class A has sufficient detail that it talks about type C (which might be an interface or a class).这意味着类 A 的契约有足够的细节来讨论类型 C(可能是接口或类)。 So yes, we're talking about a level of specification that goes beyond just "system requirements", and goes some way to describing implementation.所以,是的,我们谈论的规范级别不仅仅是“系统要求”,而且在某种程度上描述了实现。

This is normal for unit tests.这对于单元测试来说是正常的。 When you are unit testing, you want to ensure that each unit is doing the "right thing", and that will usually include its interactions with other units.当您进行单元测试时,您希望确保每个单元都在做“正确的事情”,这通常包括它与其他单元的交互。 "Units" here might mean classes, or larger subsets of your application.此处的“单元”可能是指类或应用程序的更大子集。

Update:更新:

I feel that this doesn't apply just to verification, but to stubbing as well.我觉得这不仅适用于验证,也适用于存根。 As soon as you stub a method of a collaborator class, your unit test has become, in some sense, dependent on implementation.一旦您存根合作者类的方法,您的单元测试就在某种意义上变得依赖于实现。 It's kind of in the nature of unit tests to be so.单元测试的本质就是这样。 Since Mockito is as much about stubbing as it is about verification, the fact that you're using Mockito at all implies that you're going to run across this kind of dependency.由于 Mockito 与验证一样重要,因此您完全使用 Mockito 的事实意味着您将遇到这种依赖关系。

In my experience, if I change the implementation of a class, I often have to change the implementation of its unit tests to match.根据我的经验,如果我改变一个类的实现,我经常不得不改变它的单元测试的实现来匹配。 Typically, though, I won't have to change the inventory of what unit tests there are for the class;通常情况下,虽然,我不会改变什么单元测试该类的库存; unless of course, the reason for the change was the existence of a condition that I failed to test earlier.当然,除非更改的原因是存在我之前未能测试的条件。

So this is what unit tests are about.所以这就是单元测试的意义所在。 A test that doesn't suffer from this kind of dependency on the way collaborator classes are used is really a sub-system test or an integration test.不受这种对协作者类使用方式的依赖的测试实际上是子系统测试或集成测试。 Of course, these are frequently written with JUnit too, and frequently involve the use of mocking.当然,这些也经常用 JUnit 编写,并且经常涉及到模拟的使用。 In my opinion, "JUnit" is a terrible name, for a product that lets us produce all different types of test.在我看来,“JUnit”是一个糟糕的名字,因为它是一个让我们产生所有不同类型测试的产品。

David's answer is of course correct but doesn't quite explain why you would want this.大卫的回答当然是正确的,但并没有完全解释为什么你会想要这个。

Basically, when unit testing you are testing a unit of functionality in isolation.基本上,在进行单元测试时,您是在单独测试一个功能单元。 You test whether the input produces the expected output.您测试输入是否产生预期的输出。 Sometimes, you have to test side effects as well.有时,您还必须测试副作用。 In a nutshell, verify allows you to do that.简而言之,verify 允许您这样做。

For example you have bit of business logic that is supposed to store things using a DAO.例如,您有一些业务逻辑应该使用 DAO 存储事物。 You could do this using an integration test that instantiates the DAO, hooks it up to the business logic and then pokes around in the database to see if the expected stuff got stored.您可以使用集成测试来实现这一点,该测试实例化 DAO,将其连接到业务逻辑,然后在数据库中查看是否存储了预期的内容。 That's not a unit test any more.那不再是单元测试了。

Or, you could mock the DAO and verify that it gets called in the way you expect.或者,您可以模拟 DAO 并验证它是否以您期望的方式被调用。 With mockito you can verify that something is called, how often it is called, and even use matchers on the parameters to ensure it gets called in a particular way.使用 mockito,你可以验证某个东西是否被调用,它被调用的频率,甚至可以在参数上使用匹配器来确保它以特定的方式被调用。

The flip side of unit testing like this is indeed that you are tying the tests to the implementation which makes refactoring a bit harder.像这样的单元测试的另一面确实是您将测试与实现联系起来,这使得重构有点困难。 On the other hand, a good design smell is the amount of code it takes to exercise it properly.另一方面,良好的设计气味是正确执行它所需的代码量。 If your tests need to be very long, probably something is wrong with the design.如果您的测试需要很长时间,则可能是设计出了问题。 So code with a lot of side effects/complex interactions that need to be tested is probably not a good thing to have.因此,具有许多需要测试的副作用/复杂交互的代码可能不是一件好事。

This is great question!这是个好问题! I think the root cause of it is the following, we are using JUnit not only for unit testing.我认为它的根本原因如下,我们不仅使用 JUnit 进行单元测试。 So the question should be splited up:所以这个问题应该拆分:

  • Should I use Mockito.verify() in my integration (or any other higher-than-unit testing) testing?我应该在我的集成(或任何其他高于单元的测试)测试中使用 Mockito.verify() 吗?
  • Should I use Mockito.verify() in my black-box unit-testing?我应该在我的黑盒单元测试使用 Mockito.verify() 吗?
  • Should I use Mockito.verify() in my white-box unit-testing?我应该在白盒单元测试使用 Mockito.verify() 吗?

so if we will ignore higher-than-unit testing, the question can be rephrased " Using white-box unit-testing with Mockito.verify() creates great couple between unit test and my could implementation, can I make some "grey-box" unit-testing and what rules of thumb I should use for this ".所以如果我们忽略高于单元的测试,这个问题可以改写为“使用白盒单元测试和 Mockito.verify() 在单元测试和我的可能实现之间创造了很好的结合,我可以做一些“灰盒“单元测试以及我应该使用什么经验法则”。

Now, let's go through all of this step-by-step.现在,让我们一步一步地完成所有这些。

*- Should I use Mockito.verify() in my integration (or any other higher-than-unit testing) testing?* I think the answer is clearly no, moreover you shouldn't use mocks for this. *- 我应该在我的集成(或任何其他高于单元的测试)测试中使用 Mockito.verify() 吗?* 我认为答案显然是否定的,而且你不应该为此使用模拟。 Your test should be as close to real application as possible.您的测试应尽可能接近实际应用。 You are testing complete use case, not isolated part of the application.您正在测试完整的用例,而不是应用程序的孤立部分。

* black-box vs white-box unit-testing * If you are using black-box approach what is you really doing, you supply (all equivalence classes) input, a state , and tests that you will receive expected output. *黑盒vs白盒单元测试* 如果你使用黑盒方法你真正在做什么,你提供(所有等价类)输入、状态和测试,你将收到预期的输出。 In this approach using of mocks in general is justifies (you just mimic that they are doing the right thing; you don't want to test them), but calling Mockito.verify() is superfluous.在这种方法中,一般使用模拟是合理的(你只是模仿他们在做正确的事情;你不想测试它们),但调用 Mockito.verify() 是多余的。

If you are using white-box approach what is you really doing, you're testing the behaviour of your unit.如果你正在使用白盒方法你真正在做什么,你正在测试你的单元的行为 In this approach calling to Mockito.verify() is essential, you should verify that your unit behaves as you're expecting to.在这种方法中,调用 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.在上面提到的 equals() 与 equalsIgnoreCase() 的情况下,您不应调用 Mockito.verify(),只需断言输出即可。 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).另一方面,假设您有一些 @Service 并且您正在编写本质上是 @Service 包装器的 @Web-Service - 它会将所有调用委托给 @Service (并进行一些额外的错误处理)。 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.在这种情况下,调用 Mockito.verify() 是必不可少的,您不应该重复您为 @Serive 所做的所有检查,验证您正在使用正确的参数列表调用 @Service 就足够了。

I must say, that you are absolutely right from a classical approach's point of view:我必须说,从经典方法的角度来看,您是绝对正确的:

  • 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.如果您首先创建(或更改)应用程序的业务逻辑,然后用(采用)测试Test-Last 方法覆盖它,那么让测试了解您的软件如何工作的任何信息将是非常痛苦和危险的,除了检查输入和输出。
  • 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, eg rely on some other component's method or even call it a particular amount of times.这有时意味着,您希望您的软件以某种特定方式实现,例如依赖于其他组件的方法,甚至调用它特定的次数。 That is where Mockito.verify() comes in handy!这就是Mockito.verify()派上用场的地方!

It is important to remember, that there are no universal tools.重要的是要记住,没有通用的工具。 The type of software, it's size, company goals and market situation, team skills and many other things influence the decision on which approach to use at your particular case.软件的类型、规模、公司目标和市场情况、团队技能和许多其他因素会影响在您的特定情况下使用哪种方法的决定。

As some people said正如一些人所说

  1. Sometimes you don't have a direct output on which you can assert有时您没有可以断言的直接输出
  2. Sometimes you just need to confirm that your tested method is sending the correct indirect outputs to its collaborators (which you are mocking).有时您只需要确认您的测试方法是否将正确的间接输出发送给其合作者(您正在嘲笑)。

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.我的意思是根据定义而不是关于特定的实现,例如 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.但是你可以这样想-如果你需要做的是将创建的道路上重大变化的重构你的方法的作品,这是一个好主意,做一个TDD的方式,这意味着你可以改变你的测试定义新行为(这将使测试失败),然后进行更改并再次通过测试。

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.在大多数情况下,当人们不喜欢使用 Mockito.verify 时,这是因为它用于验证被测试单元所做的一切,这意味着如果有任何变化,您将需要调整测试。 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.这是错误的思维方式。

What really is a problem, is if you can modify what your method does and a unit test which is supposed to cover the functionality entirely doesn't fail.真正有问题的是,如果您可以修改您的方法所做的事情,并且应该完全涵盖该功能的单元测试不会失败。 That would mean that whatever the intention of your change is, the result of your change isn't covered by the test.这意味着无论您更改的意图是什么,您的更改结果都不会被测试覆盖。

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.这样做时,您不仅可以使用 verify 检查是否调用了其他类的正确方法,还可以通过这些数据对象的正确方法收集传递的数据。 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.例子:如果你修改了一个db实体对象,然后使用repository保存它,仅仅验证对象的setter调用的数据是否正确,并且repository的save方法被调用是不够的。 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.所以,我不使用 Mockito.verify 但我创建了一个包含所有模拟的 inOrder 对象并使用 inOrder.verify 代替。 And if you want to make it complete, you should also call Mockito.verifyNoMoreInteractions at the end and pass it all the mocks.如果你想让它完整,你还应该在最后调用 Mockito.verifyNoMoreInteractions 并将所有模拟传递给它。 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.否则,有人可以在不测试的情况下添加新功能/行为,这意味着在您的覆盖率统计数据可以是 100% 之后,您仍然在堆积未断言或验证的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM