C# 是否建议模拟混凝土类?
mocking framework网站中给出的大多数示例都是模拟界面。假设我现在使用的NSubstitute,他们所有的模拟示例都是模拟接口C# 是否建议模拟混凝土类?,c#,unit-testing,mocking,nsubstitute,C#,Unit Testing,Mocking,Nsubstitute,mocking framework网站中给出的大多数示例都是模拟界面。假设我现在使用的NSubstitute,他们所有的模拟示例都是模拟接口 但实际上,我看到一些开发人员模拟了具体的类。建议模拟混凝土类吗?不建议这样做,但如果没有其他选择,您可以这样做 通常,设计良好的项目依赖于为单独的组件定义接口,这样您就可以通过模拟其他组件来单独测试每个组件。但是如果您使用的是遗留代码/不允许更改的代码,并且仍然希望测试您的类,那么您别无选择,也不会因此受到批评(假设您努力尝试将这些组件切换到接口,但却被拒
但实际上,我看到一些开发人员模拟了具体的类。建议模拟混凝土类吗?不建议这样做,但如果没有其他选择,您可以这样做
通常,设计良好的项目依赖于为单独的组件定义接口,这样您就可以通过模拟其他组件来单独测试每个组件。但是如果您使用的是遗留代码/不允许更改的代码,并且仍然希望测试您的类,那么您别无选择,也不会因此受到批评(假设您努力尝试将这些组件切换到接口,但却被拒绝了这样做的权利)。问题是:为什么不 我可以想出几个这样做很有用的场景,比如: 一个具体类的实现还没有完成,或者说是谁做的不可靠。因此,我模拟指定的类,并针对它测试代码
它还可以用于模拟执行数据库访问等操作的类。如果您没有测试数据库,您可能希望为测试返回始终不变的值(通过模拟类很容易实现)。理论上,模拟具体类绝对没有问题;我们是针对逻辑接口(而不是关键字
接口
)进行测试的,不管该逻辑接口是由类
还是接口
提供的
实际上.NET/C使这一点有点问题。正如您提到的.NET模拟框架,我假设您仅限于此
在.NET/C中,默认情况下,成员是非虚拟的,因此任何基于代理的模拟行为方法(即从类派生,并重写所有成员以执行特定于测试的内容)都将不起作用,除非您将成员明确标记为虚拟的
。这导致了一个问题:您正在使用模拟类的实例,该实例在单元测试中是完全安全的(即不会运行任何真实代码),但除非您确保所有内容都是虚拟的,否则最终可能会混合运行真实代码和模拟代码(如果存在构造函数逻辑,这可能会特别有问题,构造函数逻辑总是在运行,如果有其他具体的依赖项需要更新,则会变得复杂)
有几种方法可以解决这个问题
- 使用
。这是我们在中建议的工作方式,但也有一个缺点,就是可能会使用实际不需要的接口来膨胀代码库。可以说,如果我们在代码中找到良好的抽象,我们自然会得到整洁、可重用的接口,我们可以进行测试。我还没有看到这样的结果,但是YMMV.)接口
- 努力使一切虚拟化。一个有争议的缺点是,我们建议所有这些成员都是我们设计中的扩展点,而我们实际上只是想改变整个类的行为以进行测试。它也不会停止构造函数逻辑的运行,如果具体类需要其他依赖项,它也没有帮助
- 通过类似for的方式使用程序集重写,您可以使用for将程序集中的所有类成员修改为虚拟的
- 使用非基于代理的模拟库,如(付费),(付费),(需要VS Ultimate/Enterprise,尽管其前身是免费的)或(免费+开源)。我相信它们能够模拟类的所有方面,以及静态成员
希望这有帮助。更新: 3年后,我想承认我改变了主意 理论上,我仍然不喜欢仅仅为了方便创建模拟对象而创建接口。在实践中(我使用的是NSubstitute),使用
Substitute.For()
比使用多个参数模拟真实类要容易得多,例如Substitute.For(mockedParam1、mockedParam2、mockedParam3)
,其中每个参数都应该单独模拟。其他潜在故障如中所述
在我们公司,现在推荐的做法是使用接口。
原始答案:
如果您不需要创建同一抽象的多个实现,请不要创建接口。
因此,您不希望使用实际可能不需要的接口来膨胀代码库
从
您是否从类中提取接口以启用松散的
耦合?如果是这样,您可能有一个实现它们的方法。
这可能是,而且违反了规则
只有一个imple
class Foo {
fun bar() = if (someCondition) {
“Yes”
} else {
“No”
}
}
val foo = mock<Foo>()
whenever(foo.bar()).thenReturn(“Maybe”)