我们的团队正在进入TDD并努力进行单元测试的最佳实践。我们的测试代码使用依赖注入。我们的测试通常遵循Arrange-Act-Assert类型的布局,我们使用Moq模拟Arrange部分中的依赖关系。
理论上,单元测试应该是一个屏蔽,可以在你重构时保护你。但它正在变成一个阻止我们这样做的锚。我正在努力确定我们的流程失败的地方。
考虑简化的例子:
我希望控制器测试 not 不需要重构,而是向我证明我的新控制器实现符合未更改的合同。但我们在这里失败了,因为事实并非如此。
每个控制器测试都会动态地模拟存储库接口。他们都需要改变。此外,由于每个测试都不想模拟所有接口和方法,我们发现我们的测试与特定实现相关联,因为它需要知道要模拟的方法。
对于我们拥有的更多测试,重构会变得更加困难!或者更确切地说,我们模拟界面的次数越多。
所以我的问题:
是否愿意在每个测试中使用动态模拟,而不是为每个界面制作可重复使用的手工模拟?
鉴于我的故事,我是否遗漏了一些原则或陷入了常见的陷阱?
谢谢!
答案 0 :(得分:10)
你不缺少任何原则,但这是一个常见的问题。我认为每个团队都以自己的方式解决(或不解决)。
副作用
对于任何有副作用的功能,您将继续遇到此问题。我找到了副作用函数,我必须进行测试以确保以下部分或全部:
在测试中确保这一点通常意味着违反封装(我与实现交互并了解)。无论何时执行此操作,您都将隐式地将测试与实现相结合。这将导致您必须在更新正在公开/测试的实现部分时更新测试。
可重复使用的模拟
我使用了可重复使用的模拟效果很好。权衡取舍是它们的实施更复杂,因为它需要更加完整。您确实可以降低更新测试的成本以适应重构。
接受TDD
另一种选择是改变您正在测试的内容。由于这实际上是关于改变您的测试策略,因此不能轻易进入。您可能希望先进行一些分析,看看它是否真的适合您的情况。
我以前用单元测试做TDD。我遇到了一些我认为不应该处理的问题。特别是在重构周围,我注意到我们通常不得不更新许多测试。这些重构不是代码单元,而是主要组件的重组。我知道很多人会说问题是频繁的大变化,而不是单元测试。对于我们的规划/架构部分导致的大变化,可能有一些道理。但是,业务决策也导致了方向的变化。这些和其他合法原因导致需要对代码进行大量更改。最终结果是,由于所有测试更新,大型重构变得更加缓慢和痛苦。
由于单元测试未涵盖的集成问题,我们也遇到了错误。我们通过手动验收测试做了一些。我们实际上做了很多工作,使验收测试尽可能低。它们仍然是手动的,我们觉得在单元测试和验收测试之间存在很多交叉,应该有一种方法可以降低实现两者的成本。
然后该公司裁员。突然间,我们没有相同的资源来投入编程和维护。我们被推动为我们所做的一切获得最大的回报,包括测试。我们首先添加了所谓的部分堆栈测试,以涵盖我们遇到的常见集成问题。事实证明它们非常有效,我们开始进行不那么经典的单元测试。我们还摆脱了手动验收测试(Selenium)。我们慢慢推高测试开始测试的地方,直到我们基本上进行验收测试,但没有浏览器。我们将模拟GET,POST或PUT方法到特定的控制器并检查验收标准。
我们结束了更少的错误。特别是几乎所有的集成错误,以及由于大型重构导致的错误几乎完全消失。
有权衡。事实证明,专业人士远远超过了出局的缺点。缺点:
答案 1 :(得分:2)
我自己也在努力解决这类问题,并且没有一个我觉得可靠的答案,但这里有一种初步的思考方式。我观察了两种单元测试