是否建议模拟混凝土类?

Don*_*Don 35 c# unit-testing mocking nsubstitute

模拟框架网站中给出的大多数示例都是模拟接口.让我说我正在使用的NSubstitute,他们所有的模拟示例都是模拟界面.

但实际上,我看到一些开发人员嘲笑混凝土类.是否建议模拟混凝土类?

Dav*_*pak 71

从理论上讲,嘲弄具体阶级绝对没有问题; 我们正在测试逻辑接口(而不是关键字interface),并且该逻辑接口是否由class或提供无关紧要interface.

在实践中,.NET/C#使这有点问题.正如您提到的.NET模拟框架,我将假设您仅限于此.

在.NET/C#默认情况下,成员是非虚拟的,因此任何基于代理的模拟行为方法(即从类派生,并覆盖所有成员以执行特定于测试的东西)将不起作用,除非您明确标记成员作为virtual.这会导致一个问题:你正在使用一个模拟类的实例,这个实例在你的单元测试中是完全安全的(即不会运行任何真正的代码),但除非你确定一切都是virtual你可能最终得到的混合使用真实代码和模拟代码运行(如果有构造函数逻辑,这可能会特别成问题,它总是运行,并且如果有其他具体的依赖关系,则会复杂化).

有几种方法可以解决这个问题.

  • 使用interfaces.这是有效的,也是我们在NSubstitute文档中提出的建议,但是有可能使代码库膨胀的缺点是可能实际上并不需要的接口.可以说,如果我们在代码中找到好的抽象,我们自然会得到我们可以测试的整洁,可重用的接口.我还没有看到它像这样,但是YMMV.:)
  • 努力工作,让一切都变得虚拟.一个可论证的缺点是,当我们真的只想改变整个类的行为以进行测试时,我们建议所有这些成员都是我们设计中的扩展点.它也不会停止构造函数逻辑运行,如果具体类需要其他依赖项,它也不会有帮助.
  • 通过像使用汇编重写的炫技附加Fody,你可以用它来修改所有集体成员在汇编是虚拟的.
  • 使用非基于代理的模拟 库,如TypeMock (付费),JustMock (付费),Microsoft Fakes (需要VS Ultimate/Enterprise,虽然它的前身,Microsoft Moles,是免费的)Prig (免费+开源).我相信这些能够模拟类的所有方面,以及静态成员.

针对最后一个想法提出的一个常见抱怨是,您正在通过"假"接缝进行测试; 我们正在通常用于扩展代码以改变代码行为的机制之外.需要走出这些机制可能表明我们的设计具有刚性.我理解这个论点,但我已经看到了创建另一个接口的噪音超过了好处的情况.我想这是一个意识到潜在设计问题的问题; 如果您不需要测试中的反馈来突出设计刚性,那么它们就是很好的解决方案.

我要抛弃的最后一个想法是改变我们测试中单位的大小.通常我们将单个类作为一个单元.如果我们有许多内聚类作为我们的单元,并且接口充当了围绕该组件的明确定义的边界,那么我们可以避免必须模拟尽可能多的类,而只是模拟更稳定的边界.这可以使我们的测试变得更加复杂,其优势在于我们正在测试一个具有凝聚力的功能单元,并鼓励他们围绕该单元开发可靠的接口.

希望这可以帮助.


MiF*_*vil 10

更新:

3年后,我想承认我改变了主意.

即使在理论上我仍然不喜欢创建接口只是为了促进模拟对象的创建,在实践中(我使用的是NSubstitute)它更容易使用Substitute.For<MyInterface>()而不是模拟具有多个参数的真实类,例如Substitute.For<MyCLass>(mockedParam1, mockedParam2, mockedParam3),每个参数都应该被模拟分别.NSubstitute文档中描述的其他潜在问题

在我们公司,现在推荐的做法是使用接口.

原始答案:

如果您不需要创建相同抽象的多个实现,请不要创建接口.正如David Tchepak指出的那样,您不希望使用可能实际上不需要的接口来扩展您的代码库.

来自  http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstractions.aspx

您是否从类中提取接口以启用松散耦合?如果是这样,您的接口和实现它们的具体类之间可能存在  1:1的关系.这可能不是一个好兆头,违反了  重用抽象原则(RAP).

只有一个给定接口的实现是代码气味.

如果您的目标是可测试性,我更喜欢David Tchepak上面的答案中的第二个选项.

但是我不相信你必须把一切变成虚拟的.只使用虚拟方法就可以替代.我还将在方法声明旁边添加注释,该方法只是虚拟的,以使其可替代单元测试模拟.

但请注意,替换具体类而不是接口有一些限制.例如,NSubstitute

注意:不会为类创建递归替换,因为创建和使用类可能会产生潜在的不必要的副作用

.

  • 我坚信不改变代码使其更易于测试.使方法虚拟化与向类添加接口具有相同的效果.哪个看起来干净?对我来说接口呢.接口也避免调用有时非常复杂的ctor逻辑.所以IMO 1:1界面/具体类关系不是"代码嗅觉". (2认同)
  • @Ruskin你错了。使方法虚拟以进行测试是一个合法的目的。您*正在*替换其实现。事实上,您想这样做是为了测试,这表明它对于其他目的同样有效。然而,你的这个选择非常明确。这也是一个很好的健全性检查,可以确保您没有违反开闭原则并且没有泄漏抽象。 (2认同)