Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/310.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 是否建议模拟混凝土类?_C#_Unit Testing_Mocking_Nsubstitute - Fatal编程技术网

C# 是否建议模拟混凝土类?

C# 是否建议模拟混凝土类?,c#,unit-testing,mocking,nsubstitute,C#,Unit Testing,Mocking,Nsubstitute,mocking framework网站中给出的大多数示例都是模拟界面。假设我现在使用的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”)