当我最初接触 Mocks 时,我觉得主要目的是模拟来自外部数据源的对象。这样我就不必维护自动化单元测试测试数据库,我可以伪造它。

但现在我开始以不同的方式思考它。我想知道模拟是否更有效地用于将测试方法与其自身之外的任何内容完全隔离。脑海中不断浮现的图像是您绘画时使用的背景。你要防止油漆弄脏所有东西。我只是测试该方法,我只想知道它对这些伪造的外部因素有何反应?

这样做似乎非常乏味,但我看到的优点是,当测试失败时,这是因为它被搞砸了,而不是 16 层。但现在我必须进行 16 次测试才能获得相同的测试覆盖率,因为每个部分都将被单独测试。另外,每个测试都变得更加复杂,并且与它所测试的方法的联系更加紧密。

我觉得这很正确,但也显得很残酷,所以我有点想知道其他人的想法。

有帮助吗?

解决方案

我建议你看看 Martin Fowler 的文章 模拟不是存根 比我能给你的更权威的 Mocks 处理。

模拟的目的是在独立于依赖关系的情况下对代码进行单元测试,以便您可以真正在“单元”级别测试一段代码。被测试的代码是真正的代码,它所依赖的所有其他代码(通过参数或依赖项注入等)都是“模拟”(一个空的实现,当调用其方法之一时,它总是返回预期值。)

模拟一开始可能看起来很乏味,但是一旦您掌握了使用它们的窍门,它们就会使单元测试变得更容易、更健壮。大多数语言都有 Mock 库,这使得模拟变得相对简单。如果您使用 Java,我会推荐我个人最喜欢的: EasyMock。

让我以这个想法结束:您还需要集成测试,但是大量的单元测试可以帮助您找出哪个组件包含错误(当存在错误时)。

其他提示

卢克大师,不要走上黑暗的道路。:) 不要嘲笑一切。你可以,但你不应该...这就是原因。

  • 如果您继续单独测试每种方法,那么当您将它们全部组合在一起时,您会发现惊喜并为您做好准备 大爆炸. 。我们构建对象,以便它们可以共同解决更大的问题。就其本身而言,它们是微不足道的。你 需要知道所有协作者是否按预期工作.
  • 模拟 使测试变得脆弱 通过引入重复 - 是的,我知道这听起来令人震惊。对于您设置的每个模拟,都有 n 个地方存在您的方法签名。实际代码和您的模拟期望(在多个测试中)。更改实际代码更容易......更新所有模拟期望是乏味的。
  • 你的 test 现在了解内部实施信息. 。因此,您的测试取决于您选择如何实施解决方案......坏的。测试应该是一个独立的规范,可以通过多种解决方案来满足。我应该可以自由地在代码块上按删除键并重新实现 没有 必须重写测试套件..因为要求仍然保持不变。

最后,我会说“如果它像鸭子一样嘎嘎叫,像鸭子一样走路,那么它可能是一只鸭子” - 如果感觉不对......可能是这样。*使用模拟来抽象出问题子项,例如 IO 操作、数据库、第三方组件等。就像盐一样,有些是必需的。太多了并且:x *
这是基于状态的测试与基于迭代的测试的圣战。谷歌搜索会给你更深入的了解。

澄清:我遇到了一些阻力。这里进行集成测试:)所以为了澄清我的立场..

  • 模拟不属于“验收测试”/集成领域。您只能在单元测试世界中找到它们。这就是我的重点。
  • 验收测试是不同的 非常 需要——而不是贬低他们。但单元测试和验收测试是不同的,应该保持不同。
  • 组件或包中的所有协作者不需要彼此隔离。就像微优化一样,这是过度杀伤力。它们的存在是为了解决问题 一起..凝聚。

是的我同意。我认为嘲笑有时是痛苦的,但通常是必要的,这样你的测试才能真正成为 单元 测试,即只有您可以进行测试的最小单元才被测试。这使您可以消除可能影响测试结果的任何其他因素。您最终确实会进行更多的小测试,但找出代码的问题所在变得更加容易。

我的理念是你应该编写可测试的代码来适应测试,
不编写测试来适应代码。

至于复杂性,我的观点是测试应该易于编写,因为如果是的话,您可以编写更多测试。

我可能同意,如果您正在模拟的类没有测试套件,这可能是个好主意,因为如果他们确实有适当的测试套件,您就会知道问题出在哪里而无需隔离。

我使用模拟对象的大多数时间是当我编写测试的代码是如此紧密耦合时(阅读:糟糕的设计),当它们依赖的类不可用时,我必须编写模拟对象。当然,模拟对象有有效的用途,但是如果您的代码 需要 他们的用法,我会再看一下设计。

是的,这就是使用模拟测试的缺点。你需要做很多工作,这感觉很残酷。但这就是单元测试的本质。如果不模拟外部资源,如何单独测试某些东西?

另一方面,您正在嘲笑缓慢的功能(例如数据库和 I/O 操作)。如果测试运行得更快,那么程序员就会感到高兴。没有什么比等待非常慢的测试更痛苦的了,当您尝试实现一项功能时,测试需要超过 10 秒才能完成运行。

如果项目中的每个开发人员都花时间编写单元测试,那么这 16 层(间接)就不是什么大问题。希望您从一开始就应该进行测试覆盖,对吧?:)

另外,不要忘记在协作对象之间编写功能/集成测试。否则你可能会错过一些东西。这些测试不需要经常运行,但仍然很重要。

是的,在某种程度上,模拟旨在用于模拟外部数据源,例如数据库或 Web 服务。然而,在更细粒度的范围内,如果您正在设计松散耦合的代码,那么您可以在整个代码中几乎任意地画线,以确定任何时候可能是“外部系统”。拿我目前正在做的一个项目来说:

当有人尝试签入时, 签到界面 发送一个 签到信息 反对 签入调解员 使用验证它的对象 签入验证器, ,那么如果没问题的话,它会填充一个名为的域对象 交易签到信息 使用 签入信息适配器 然后通过 交易 到一个实例 ITransactionDao.SaveTransaction() 为了坚持。

我现在正在写一些自动化的 集成测试 显然 签到界面交易道 是外部系统的窗口,它们是应该被嘲笑的。然而,谁能在某个时候说 签入验证器 不会调用网络服务吗?这就是为什么当你写的时候 单元测试 您假设类的特定功能之外的所有内容都是外部系统。因此在我的单元测试中 签入调解员 我模拟了所有与它对话的对象。

编辑: Gishu 在技术上是正确的,并不是所有东西都需要嘲笑,例如我不嘲笑 签到信息 因为它只是一个数据容器。然而,任何你能看到的外部服务(几乎任何转换数据或有副作用的东西)都应该被嘲笑。

我喜欢的一个类比是将适当松散耦合的设计视为一个领域,人们站在它周围玩接球游戏。当有人传球时,他可能会向下一个人扔一个完全不同的球,甚至可能会连续向不同的人扔多个球,或者扔出一个球并等待收到球后再将其扔给另一个人。这是一个奇怪的游戏。

现在作为他们的教练和经理,你当然想要检查你的团队作为一个整体是如何运作的,这样你就可以进行团队练习(集成测试),但你也可以让每个球员单独练习对抗后卫和投球机(单元测试与嘲笑)。这张照片唯一缺少的部分是模拟期望,所以我们把我们的球涂上了黑色焦油,这样当它们击中后挡板时就会弄脏它。每个逆止器都有一个该人瞄准的“目标区域”,如果在练习跑结束时目标区域内没有黑色标记,您就知道出了问题,并且该人需要调整他的技术。

真的花时间正确地学习它,我理解 Mocks 的那一天是一个巨大的顿悟时刻。将它与控制反转容器结合起来,我就再也不会回去了。

顺便说一句,我们的一位 IT 人员刚刚进来并给了我一台免费的笔记本电脑!

正如有人之前所说,如果您模拟所有内容以隔离比您正在测试的类更细粒度的内容,那么您就放弃了在正在测试的代码中强制执行内聚性。

请记住,模拟有一个基本优势:行为验证。这是存根不提供的东西,也是使测试更加脆弱的另一个原因(但可以提高代码覆盖率)。

模拟的发明部分是为了 回答问题:如果对象没有 getter 或 setter,您将如何对对象进行单元测试?

这些日子, 受到推崇的 实践是模拟角色而不是对象。使用 Mocks 作为设计工具来讨论协作和职责分离,而不是作为“智能存根”。

模拟对象 1) 通常用作隔离被测代码的手段,但是 2) 正如 keithb 已经指出的那样,对于“关注协作对象之间的关系”。本文提供了与该主题相关的一些见解和历史: 使用模拟对象的责任驱动设计.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top