什么是好的策略?在 C# 中使用模拟进行测试,无需默认的虚拟



我遇到了C#在嘲讽和测试方面的一个很大的不足。我对这个问题的解决方案是不可取的。

我有三个类,它们组合在一起执行一些功能。使用接口或显式声明任何方法都是虚拟的没有任何意义,因为设计并没有真正调用扩展或多态性。任何使类可重用的努力都只会使代码复杂化。

然而,由于我还没有明确声明任何方法是虚拟的,所以我无法模拟这些类并通过框架记录它们的调用。伪代码(使用Rhino.Mocks)看起来像这样。

var b = mockRepo.StrickMock<ClassB>();
var c = mockRepo.StrickMock<ClassC>();
var classUnderTest = new ClassUnderTest{ B = b, C = c};
Expect.Call( b.MethodA );
Expect.Call( c.MethodB );
mockRepo.ReplayAll();
classUnderTest.DoSomething();
mockRepo.VerifyAll();

按照目前的情况,我必须将b.MethodA和c.MethodB虚拟化才能实现这一点。或者,我可以提取ClassB和ClassC的接口并模拟它们。但正如我之前所说,这只会使事情复杂化。我可以更改类的设计,使其更可重用,但这也会使问题复杂化,并且功能重用的机会非常低。

我应该如何解决这个问题,并且仍然努力保持依赖项和执行代码之间的简单性?我错过了一个选项吗?你更愿意采取什么方法?

接口是单元测试的一个接缝,可以轻松插入fakes。我不认为它们会使事情复杂化(除了增加类型的数量)。

接口使调用者和被调用者之间的契约显式。接口还提供了创建"名称系统"的机会。该接口还可以更容易地查看不属于的成员(而不是可以吸引不相关方法的类)。

总之,付出的代价很小。

通过确保代码以某种方式调用其依赖项的例程来测试功能并不理想。您的测试现在与方法的实现绑定,而不是与您期望的行为绑定。

我建议从以下问题开始:我要测试哪些行为?行为是一组先决条件、一个要调用的操作(即方法调用)和一组要测试的后条件。例如,如果你想测试将一个项目推到堆栈上会使其计数增加一,你可以编写一个名为:的测试

public void Push_OnAnyStack_IncreasesCountByOne()

一般来说,您可以使用以下模板命名测试:

public void MethodToCall_WithGivenStateAndInputs_PerformsExpectedResult

我同意你的观点,即为了接口而引入接口可能过于工程化。因此,您应该查看您的代码结构,看看是否有一种方法可以通过设置先决条件(安排测试)、通过调用特定方法进行操作以及通过查询正在测试的对象的状态来断言后决条件来测试要测试的行为。这样,您就不太关心方法是如何实现的,而更关心它执行的行为。如果你发现这是不可能的,这表明你需要重构你的设计以明确责任。这也可能表明接口实际上是正确的解决方案。

SOLID原则中反复出现的主题之一是,您应该依赖抽象(开闭原则/依赖反转原则)。无论您使用接口还是带有虚拟方法的抽象类,mocking都是关于测试对象之间的关系或契约。

如果使用模拟框架,这些工具将使用动态代理动态生成基于行为的验证的实现。这种策略意味着您使用的是可以替换的抽象。

TypeMock等其他框架使用Profiler API在编译JIT之前拦截并重写IL。这是一款付费产品,速度有点慢,但它可以让你拦截任何东西。

就我个人而言,如果有选择的话。我会使用免费的工具,设计抽象。代码将不那么灵活,但总体上更可预测,更易于测试。

我肯定会选择接口。

最新更新