0

我正在尝试学习单元测试,但会导致设计问题。考虑类 A 依赖于类 B。如果要为 B 创建存根以便对 A 进行单元测试,大多数隔离框架要求 B 必须是接口,或者 A 使用的所有方法都必须是虚拟的。B 本质上不能是具有非虚拟方法的具体类,以便进行单元测试。

这对生产代码的设计施加了重大限制。如果我必须为每个依赖项创建一个接口,那么类的数量将增加一倍。遵循单一职责原则会导致小类相互依赖,因此这会增加接口的数量。我还为易失性依赖项(将来可能会更改)或设计需要它以实现可扩展性创建接口。仅仅为了测试而使用接口污染生产代码将显着增加其复杂性。使所有方法都虚拟化似乎也不是一个好的解决方案。它给继承者的印象是这些方法可以被覆盖,即使它们不是,实际上这只是单元测试的副作用。

这是否意味着可测试的面向对象设计不允许具体的依赖关系,或者是否意味着不应该伪造具体的依赖关系?“必须伪造每个依赖项(存根或模拟)才能正确进行单元测试”是我到目前为止所学到的,所以我不认为后者是这种情况。JustMock 和 Isolator 之外的隔离框架不允许在没有虚拟方法的情况下伪造具体的依赖关系,有些人认为 JustMock 和 Isolator 的强大功能会导致糟糕的设计。我认为模拟任何类的能力非常强大,如果你知道自己在做什么,它将保持生产代码的设计干净。

4

1 回答 1

0

后来我意识到这个问题也问了同样的问题,似乎没有解决方案。在创建接口或使所有方法虚拟化之间进行选择是 C# 的限制,它是静态类型语言。像 Ruby 这样的鸭子类型语言不会强加这一点,并且可以轻松创建假对象而无需更改原始类。在 Ruby 中,假对象只需要创建适当的方法,就可以使用它来代替原始依赖项。

编辑:

我读完了 Roy Osherove 的《单元测试的艺术》一书,发现以下段落是相关的:

可测试的设计通常只在静态语言中很重要,例如 C# 或 VB.NET,其中可测试性取决于允许替换事物的主动设计选择。在更动态的语言中,为可测试性设计的重要性较小,默认情况下事情更容易测试。在这样的语言中,无论项目设计如何,大多数东西都很容易替换。这使此类语言的社区摆脱了稻草人的论点,即代码缺乏可测试性意味着它的设计很糟糕,并让他们在更深层次上专注于好的设计应该实现的目标。

可测试的设计具有虚拟方法、非密封类、接口和清晰的关注点分离。它们有更少的静态类和方法,以及更多的逻辑类实例。事实上,可测试的设计与 SOLID 设计原则相关,但并不一定意味着你有一个好的设计。也许是时候最终目标不应该是可测试性,而应该是良好的设计了。

这基本上意味着由于静态语言的限制而使设计可测试并不能使其本质上成为“好的设计”。对我来说,一个好的设计可以满足当今的需求,并且不会过多地考虑未来。当然,将每个依赖抽象化有利于未来的可维护性,但它会使 API 变得非常复杂。如果依赖项可能会更改,或者许多具体类实现该接口,而不是因为可测试性需要它,我想将依赖项设为接口。这样做是因为可测试性要求它会导致“糟糕的设计”。

于 2015-10-31T07:38:11.217 回答