在代码中(而非测试中)使用NSubstitute(或其他允许模拟的测试框架)是一种好习惯吗?

时间:2018-11-15 15:32:34

标签: c# .net unit-testing mocking nsubstitute

我在业务逻辑内部遇到了NSubstitute的使用(在测试分类之外):

var extension = Substitute.For<IExtension>();

当您需要模拟某些类(接口)时,我习惯于在测试类中利用NSubstitute。但是在测试类之外使用NSubstitute使我感到困惑。这是正确的地方吗?使用像依赖注入容器这样的NSubstitute是正确的,它可以创建接口/类的实例吗?

我担心的是NSubstitute设计用于测试。测试内部的性能不是很重要,这就是为什么它可能很慢的原因。而且,它依赖于反射,因此不可能很快。但是,NSubstitute的性能不好吗?

还有其他原因,为什么不应该在测试之外使用NSubstitute或其他模拟库?

1 个答案:

答案 0 :(得分:3)

不,在生产代码中使用模拟库通常不是一个好习惯。 (我将在这里大量使用“一般”,因为我认为关于“良好实践”的任何问题都需要一定程度的概括。人们可能会提出反对这种概括的案例,但我认为这些案例将是少数。)

即使不考虑性能,模拟库也会为接口/类创建测试实现。测试实现通常支持一些功能,例如记录调用并存入特定调用以返回特定结果。通常,当我们具有用于生产代码的接口或类时,它是为了达到某些特定目的,而不是记录调用和存根返回值的一般目的。

虽然可以使用NSubstitute为接口提供特定的实现,并且可以对执行生产代码逻辑的每个调用进行存根,但为什么不创建具有所需实现的类呢?

这通常具有以下优点:

  • 实现起来应该更简洁(否则,请考虑切换到更好的语言!:D)
  • 使用编程语言的本机结构
  • 应具有更好的性能(删除模拟库所需的间接访问级别)

特别是对于NSubstitute来说,有一些重要的原因导致您永远不要在生产代码中使用它。因为该库是为测试代码设计的,所以它使用了一些生产代码不可接受的方法:

  • 它使用全局状态来支持其语法。
  • 它滥用C#/ VB语法进行测试(几乎可以认为是测试DSL)。例如说sub.MyCall()返回一个int。像sub.MyCall().Returns(42)这样的调用进行存根意味着我们正在调用int.Returns(42),这现在将以某种方式影响正在被调用的int之外的调用的返回值。这与C#/ VB通常的工作方式完全不同。
  • 所有内容都需要虚拟成员。许多模拟库都共享此约束。对于NSubstitute,如果将其与非虚拟成员一起使用,可能会得到不可预测的结果。对于生产代码而言,不可预测性不是一件好事。
  • 测试通常是短暂的。 NSubstitute(可能还有其他库)可以根据短期对象来制定实现决策。
  • 测试显示特定案例的通过或失败。这意味着如果NSubstitute中存在问题,可以在尝试编写和运行特定测试时立即将其提起。尽管在NSubstitute的质量和可靠性方面进行了大量工作,但C#编译器所进行的工作量和检查工作却完全不同。对于生产代码,我们希望有一个非常稳定的基础作为基础。

总结:您的编程语言提供了为实现逻辑接口而设计和优化的结构。您的模拟库提供了为更有限的任务而设计和优化的构造,这些任务提供了逻辑接口的测试实现,以便与测试代码结合使用,而与其依赖关系无关。除非您出于铁定的理由而进行编程,就像用纸而不是铲子挖一个洞一样,我建议您将每种工具用于预期的用途。 :)