unit-testing - 可重用的模拟与每个测试中的模拟

标签 unit-testing mocking moq

我们的团队正在努力开发TDD,并努力进行单元测试的最佳实践。我们的测试代码使用依赖注入(inject)。我们的测试通常遵循Arrange-Act-Assert类型的布局,在该布局中,我们使用Moq模拟Arrange部分中的依赖项。

从理论上讲,单元测试应该是在重构时保护您的屏障。但是它正在变成阻碍我们这样做的 anchor 。我正在尝试确定过程失败的原因。

考虑简化的示例:

  • XRepository.Save的签名和行为/契约(Contract)已更改。
  • XController.Save使用XRepository.Save,因此可以重构为使用新接口(interface)。但是从外部看,它的公共(public)契约(Contract)没有改变。

  • 我希望不需要重构 Controller 测试,而是向我证明我的新 Controller 实现遵循未更改的契约(Contract)。但是我们在这里失败了,因为事实并非如此。

    每个 Controller 测试都会动态模拟存储库接口(interface)。它们都需要更改。此外,由于每个测试都不想模拟所有接口(interface)和方法,因此我们发现测试与特定的实现相关,因为它需要知道要模拟的方法。

    对于我们拥有的更多测试,重构变得越来越困难!或更准确地说,我们模拟接口(interface)的次数越多。

    所以我的问题是:
  • 在每个测试中使用即时模拟与为每个接口(interface)制作可重复使用的手工模拟相比有偏好吗?
  • 考虑到我的故事,我是否错过了一些原则或陷入一个共同的陷阱?

  • 谢谢!

    最佳答案

    您不会错过任何原则,但这是一个普遍的问题。我认为每个团队都以自己的方式解决(或不解决)。

    副作用

    任何具有副作用的功能都将继续出现此问题。我发现有副作用功能,我必须进行测试以确保某些或所有以下各项:

  • 以前叫/不叫
  • 被称为
  • 的次数
  • 传递了哪些参数
  • 调用顺序

  • 在测试中确保这一点通常意味着违反封装(我与实现进行交互并知道实现)。每当您执行此操作时,您总是会隐式地将测试与实现耦合。每当您更新要公开/测试的实现部分时,这将导致您必须更新测试。

    可重复使用的 mock

    我已经使用了可重复使用的模拟来取得巨大的效果。折衷是它们的实现更加复杂,因为它需要更完整。您确实减少了更新测试以适应重构的成本。

    接受TDD

    另一个选择是更改要测试的内容。由于这确实是在改变您的测试策略,因此请不要轻描淡写。您可能需要先做一点分析,看看它是否真的适合您的情况。

    我曾经用单元测试来做TDD。我遇到了一个我认为不应该解决的问题。特别是在重构方面,我注意到我们通常必须更新许多测试。这些重构不在代码单元之内,而是对主要组件的重构。我知道很多人会说问题是频繁的大更改,而不是单元测试。大的变化可能是有些道理,这部分是我们的计划/架构造成的。但是,导致方向发生变化的业务决策也是如此。这些和其他合理的原因导致必须对代码进行大量更改。最终结果是,由于所有测试更新,大型重构变得更加缓慢和痛苦。

    由于单元测试未涵盖的集成问题,我们还遇到了错误。我们通过人工验收测试做了一些。实际上,我们做了很多工作来使验收测试尽可能地降低接触性。它们仍然是手动的,我们觉得在单元测试和验收测试之间有太多的交叉,应该有一种方法来减轻两者的成本。

    然后公司裁员。突然之间,我们没有足够的资源来进行编程和维护。我们被迫在包括测试在内的所有工作中获得最大的返回。我们从添加所谓的部分堆栈测试开始,以解决我们遇到的常见集成问题。事实证明它们是如此有效,以至于我们开始进行较少的经典单元测试。我们还摆脱了手工验收测试( Selenium )。我们慢慢提高了测试开始进行测试的位置,直到我们基本上在进行验收测试,但没有使用浏览器。我们将针对特定 Controller 模拟GET,POST或PUT方法,并检查接受标准。
  • 数据库已正确更新
  • 返回了正确的HTTP状态代码
  • 返回了以下页面:
  • 是有效的html 4.01严格
  • 包含我们想要发送回给用户的信息

  • 我们结束了更少的错误。具体而言,几乎所有集成错误以及由于大型重构而导致的错误几乎完全消失了。

    权衡取舍。事实证明,有利条件远远超过了不利条件。缺点:
  • 测试通常更加复杂,几乎每个人都测试一些副作用。
  • 我们可以分辨出什么时候发生了问题,但是它不像单元测试那样具有针对性,因此我们必须进行更多的调试才能找出问题所在。
  • 关于unit-testing - 可重用的模拟与每个测试中的模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4628294/

    相关文章:

    c# - 如何模拟 Microsoft Graph API SDK 客户端?

    c# - Moq.Mock.Verify() 是否使用身份或.Equals() 比较参数?

    c# - 如何在 C# 中模拟来自 httpclient 的响应,Azure 函数,如何测试

    iphone - iPhone 上的单元测试无法构建

    php - @codeCoverageIgnoreStart 无法正常工作

    unit-testing - RhinoMock vs. TypeMock vs. NUnit 的 Mocking?

    go - 使用 go-sqlmock 在 SQL 查询中获取参数值

    typescript - 在 Jest 中,如何仅在特定测试中模拟外部模块?

    java - Easymock.expect 抛出 IllegalStateException

    ruby-on-rails - 测试被调用两次的 RSpec 模拟