关于java:何时使用Mockito.verify()?

When to use Mockito.verify()?

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

  • 在所有(或大多数)输入组合/值下,确保我的代码满足所有必需的功能。
  • 为了确保我可以更改实现,并依赖JUnit测试用例告诉我所有功能仍然满足。
  • 作为我的代码处理的所有用例的文档,并作为重构规范 - 如果代码需要重写。 (重构代码,如果我的jUnit测试失败 - 你可能错过了一些用例)。
  • 我不明白为什么或何时应该使用Mockito.verify()。当我看到verify()被调用时,它告诉我我的jUnit正在意识到实现。 (因此,即使我的功能未受影响,更改我的实现也会破坏我的jUnits)。

    我在找:

  • 适当使用Mockito.verify()的准则应该是什么?

  • 从根本上来说,jUnits是否能够了解或紧密耦合到被测试类的实现?


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

    这意味着类A的合同具有足够的细节,它涉及类型C(可能是接口或类)。所以,是的,我们讨论的规范水平不仅仅是"系统要求",而是在某种程度上描述了实施。

    这对于单元测试来说是正常的。当您进行单元测试时,您希望确保每个单元都在做"正确的事情",这通常包括它与其他单元的交互。这里的"单位"可能表示应用程序的类或更大的子集。

    更新:

    我觉得这不仅适用于验证,也适用于存根。一旦你存在一个协作者类的方法,你的单元测试在某种意义上已经变得依赖于实现。单元测试的本质就是如此。由于Mockito与验证有关,所以你使用Mockito这一事实意味着你将遇到这种依赖。

    根据我的经验,如果我改变一个类的实现,我经常要改变其单元测试的实现来匹配。但是,通常情况下,我不必更改课程单元测试的库存;除非当然,改变的原因是存在我之前未能测试的情况。

    所以这就是单元测试的内容。不受这种对协作者类使用方式的依赖的测试实际上是子系统测试或集成测试。当然,这些也经常用JUnit编写,并且经常涉及使用模拟。在我看来,"JUnit"是一个糟糕的名字,对于一种让我们可以生产所有不同类型的测试的产品。


    大卫的答案当然是正确的,但并不能完全解释为什么你会想要这个。

    基本上,在进行单元测试时,您正在单独测试一个功能单元。您测试输入是否产生预期输出。有时,您还必须测试副作用。简而言之,验证允许您这样做。

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

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

    像这样的单元测试的另一面确实是你将测试绑定到实现上,这使得重构变得更加困难。另一方面,良好的设计气味是正确运用它所需的代码量。如果您的测试需要很长时间,那么设计可能出现问题。因此,需要测试的具有大量副作用/复杂交互的代码可能不是一件好事。


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

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

    因此,如果我们忽略高于单位的测试,问题可以改写为"使用Mockito.verify()的白盒单元测试在单元测试和我可以实现之间创建了很好的结合,我可以制作一些"灰盒子"单元测试以及我应该使用的经验法则"。

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

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

    *黑盒与白盒单元测试*
    如果你正在使用黑盒方法你正在做什么,你提供(所有等价类)输入,状态,并测试你将收到预期的输出。在这种方法中,使用模拟通常是有道理的(你只是模仿它们正在做正确的事情;你不想测试它们),但是调用Mockito.verify()是多余的。

    如果你正在使用白盒方法你正在做什么,你正在测试你的单位的行为。在这种方法中,调用Mockito.verify()是必不可少的,您应该验证您的单元的行为与您期望的一样。

    灰盒子测试的拇指规则
    白盒测试的问题在于它会产生高耦合。一种可能的解决方案是进行灰盒测试,而不是白盒测试。这是黑盒和白盒测试的组合。您正在测试单元的行为,就像在白盒测试中一样,但一般情况下,如果可能,您可以使其与实现无关。如果可能的话,你只需要像黑盒一样进行检查,只是断言输出是你预期的结果。所以,你的问题的本质是什么时候有可能。

    这真的很难。我没有一个很好的例子,但我可以举例说明。在上面用equals()和equalsIgnoreCase()提到的情况下,你不应该调用Mockito.verify(),只是断言输出。如果您不能这样做,请将代码分解为较小的单元,直到您可以执行此操作。另一方面,假设您有一些@Service,并且您正在编写基本上是@Service的包装的@Web-Service - 它将所有调用委托给@Service(并进行一些额外的错误处理)。在这种情况下,调用Mockito.verify()是必不可少的,你不应该复制你为@Serive做的所有检查,验证你使用正确的参数列表调用@Service就足够了。


    我必须说,从经典方法的角度来看,你是完全正确的:

    • 如果您首先创建(或更改)应用程序的业务逻辑,然后使用(采用)测试(测试最后方法)来覆盖它,那么让测试知道您的软件如何工作将是非常痛苦和危险的,除了检查输入和输出。
    • 如果您正在练习测试驱动的方法,那么您的测试将首先编写,更改并反映软件功能的用例。实施取决于测试。这有时意味着,您希望以某种特定方式实施您的软件,例如依赖于某些其他组件的方法甚至称它为特定次数。这就是Mockito.verify()派上用场的地方!

    重要的是要记住,没有通用的工具。软件的类型,规模,公司目标和市场情况,团队技能以及许多其他因素会影响您在特定情况下使用哪种方法的决策。


    有些人说

  • 有时您没有可以断言的直接输出
  • 有时您只需要确认您的测试方法是向其协作者(您正在嘲笑)发送正确的间接输出。
  • 关于您在重构时打破测试的问题,在使用模拟/存根/间谍时会有所预期。 我的意思是按照定义,而不是像Mockito这样的具体实施。
    但是你可以用这种方式思考 - 如果你需要进行重构会对你的方法工作方式产生重大改变,那么在TDD方法上做这个是个好主意,这意味着你可以先改变测试来定义 新行为(将导致测试失败),然后执行更改并再次通过测试。