代码之家  ›  专栏  ›  技术社区  ›  staszko032

Mockito以干净的代码方式验证[副本]

  •  0
  • staszko032  · 技术社区  · 8 年前

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

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

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

    我在找:

    1. 如何正确使用 mockito.verify()验证 ?

    2. JUnits是否从根本上正确地意识到或紧密地耦合到被测试类的实现?

    0 回复  |  直到 8 年前
        1
  •  70
  •   tkruse    8 年前

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

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

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

    更新:

    我觉得这不仅适用于验证,也适用于存根。一旦您截取了一个合作者类的方法,在某种意义上,您的单元测试就依赖于实现。这样做有点像单元测试的性质。因为mockito和验证一样重要,所以你使用mockito的事实意味着你将遇到这种依赖。

    在我的经验中,如果我改变一个类的实现,我经常需要改变它的单元测试的实现来匹配。不过,通常情况下,我不需要更改那里哪些单元测试的清单。 是 对于这门课,除非,当然,改变的原因是存在一个我之前没有测试过的条件。

    这就是单元测试的意义所在。不受这种依赖于合作者类使用方式的影响的测试实际上是子系统测试或集成测试。当然,这些通常也是用JUnit编写的,并且经常涉及到模拟的使用。在我看来,“junit”是一个可怕的名字,对于一个能让我们产生所有不同类型测试的产品来说。

        2
  •  53
  •   Jilles van Gurp    14 年前

    大卫的回答当然是正确的,但不能很好地解释为什么你会想要这个。

    基本上,当单元测试时,您是在单独测试功能单元。测试输入是否产生预期的输出。有时,你也必须测试副作用。简言之,验证允许您这样做。

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

    或者,您可以模拟DAO并验证它是否按预期的方式被调用。使用mockito,您可以验证是否调用了某个对象,调用的频率,甚至在参数上使用matchers来确保以特定的方式调用它。

    像这样的单元测试的另一面实际上是将测试与实现联系起来,这使得重构变得更加困难。另一方面,一个好的设计气味是正确地运用它所需要的代码量。如果您的测试需要很长时间,可能是设计出了问题。因此,需要测试具有许多副作用/复杂交互的代码可能不是一件好事。

        3
  •  26
  •   alexsmail    14 年前

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

    • 我应该在我的 集成 (或任何其他高于单元测试的)测试?
    • 我应该在我的 黑匣子 单元测试?
    • 我应该在我的 白盒 单元测试?

    因此,如果我们忽略高于单元的测试,那么可以重新表述问题。” 使用 白盒 使用mockito.verify()进行单元测试在单元测试和我的can实现之间创建了很好的耦合,我可以做些什么吗? “灰色框” 单元测试以及我应该使用什么样的经验法则 “。

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

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

    * 黑匣子 VS 白盒 单元测试* 如果您正在使用 黑匣子 接近你真正在做什么,你提供(所有等价类)输入,一个 状态 ,并测试您将收到预期的输出。在这种方法中,使用mocks通常是合理的(您只是模仿它们做了正确的事情;您不想测试它们),但是调用mockito.verify()是多余的。

    如果您正在使用 白盒 接近你真正在做什么,你正在测试 行为 你的单位。在这种方法中,调用mockito.verify()是必需的,您应该验证您的单元的行为是否如预期的那样。

    灰盒试验拇指规则 白盒测试的问题在于它会产生高耦合。一个可能的解决方案是做灰盒测试,而不是白盒测试。这是黑盒和白盒测试的组合。你真的在测试 行为 与白盒测试类似,但一般来说,您使其实现不可知论 如果可能的话 . 如果可能的话,您只需像在黑盒中那样进行一次检查,只需断言输出是您期望的输出。所以,你问题的本质是什么时候可能。

    这真的很难。我没有一个好的例子,但我可以给你举几个例子。在上面提到的equals()与equalSignoreCase()的情况下,您不应该调用mockito.verify(),只需断言输出即可。如果你做不到,把你的代码分解成更小的单元,直到你能做到。另一方面,假设您有一些@service,并且您正在编写@web service,它本质上是包装在您的@service上的—它将所有调用委托给@service(并进行一些额外的错误处理)。在这种情况下,调用mockito.verify()是必需的,您不应该重复为@serive所做的所有检查,验证您是否使用正确的参数列表调用@service就足够了。

        4
  •  8
  •   hammelion    11 年前

    我必须说,从经典的观点来看,你是绝对正确的:

    • 如果你先来 创建(或更改)业务逻辑 你的申请,然后 用(采用)试验覆盖它 ( 测试最后一个方法 ,那么让测试了解软件的工作原理将是非常痛苦和危险的,除了检查输入和输出。
    • 如果你在练习 Test-Driven approach ,那么你的测试是 first to be written, to be changed and to reflect the use cases 软件的功能。 实现依赖于测试。 这有时意味着,您希望以某种特定的方式实现您的软件,例如依赖其他组件的方法,甚至调用它特定的次数。那是哪里 Mockito.verify() 派上用场!

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

        5
  •  0
  •   Emanuel Luiz Lariguet Beltrame    9 年前

    正如一些人所说

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

    关于重构时破坏测试的问题,这在使用mock/stub/spies时是有点期望的。我的意思是,根据定义,而不是关于一个具体的实现,比如mockito。 但是你可以这样想——如果你需要做一个重构,在你的方法的工作方式上产生重大的改变,那么在TDD方法上做是一个好主意,这意味着你可以改变你的测试。 第一 定义新行为(将使测试失败),以及 然后 进行更改并再次通过测试。