我正在尝试学习单元测试,但是由此产生了一个设计问题。考虑类A依赖于类B.如果要为B单元测试A创建存根,则大多数隔离框架要求B必须是接口,或者A使用的所有方法必须是虚拟的。 B本质上不是具有非虚方法的具体类,以便进行单元测试。
这对生产代码的设计施加了重大限制。如果我必须为每个依赖项创建一个接口,那么类的数量将加倍。遵循单一责任原则会导致相互依赖的小类,从而炸掉接口数量。此外,我还为易失性依赖项创建接口(将来可能会更改),或者设计是否需要它以实现可扩展性。使用仅用于测试的接口来污染生产代码将显着增加其复杂性。 使所有方法虚拟化似乎也不是一个好的解决方案。它给了继承者一种印象,即即使它们不是这些方法也可以被覆盖,实际上这只是单元测试的副作用。
这是否意味着可测试的面向对象设计不允许具体的依赖性,或者它是否意味着具体的依赖关系不应该被伪造? "每个依赖项必须伪造(存根或模拟)才能正确地进行单元测试"是我到目前为止所学到的,所以我不认为后者是这样的。除了JustMock和Isolator之外的隔离框架不允许在没有虚拟方法的情况下伪造具体的依赖关系,并且有些人认为JustMock和Isolator的功能导致糟糕的设计。我认为模拟任何类的能力非常强大,如果你知道你在做什么,它将保持生产代码的设计清洁。
答案 0 :(得分:0)
后来我意识到这个question也问了同样的问题,似乎没有办法解决。在创建接口或将所有方法设置为虚拟之间进行选择是对C#的限制,这是静态类型语言。诸如Ruby之类的Duck类型的语言并没有强加于此,并且可以在不改变原始类的情况下轻松创建伪对象。在Ruby中,假对象只需要创建适当的方法,它可以用来代替原始的依赖。
编辑:
我读完了Roy Osherove的单元测试艺术,发现以下段落是相关的:
可测试设计通常只在静态语言中有用,例如C#或VB.NET, 可测试性取决于允许更换物品的主动设计选择。 对于可测试性而言,设计在更动态的语言中更为重要 默认情况下更可测试。在这些语言中,大多数东西都很容易更换, 无论项目设计如何。这使得这些语言的社区摆脱了困境 稻草人认为缺乏代码的可测试性意味着它的设计很糟糕 让他们在更深层次上关注优秀设计应该达到的目标。
可测试设计具有虚拟方法,非密封类,接口和清晰 关注点分离。它们具有较少的静态类和方法,以及更多 逻辑类的实例。实际上,可测试设计与SOLID设计原则相关 但并不一定意味着你有一个好的设计。也许现在是最终目标的时候了 不应该是可测试性,而是单独的好设计。
这基本上意味着,由于静态语言的限制而使设计可测试并不能使它成为一个好的设计"本质。对我来说,一个好的设计可以满足当今的需求,并且不会过多地考虑未来。当然,将每个依赖项抽象化都有利于将来的可维护性,但它会使API变得非常复杂。我希望将依赖关系作为一个接口,如果它可能会改变或许多具体类实现该接口而不是因为可测试性需要它。这样做是因为可测性要求导致设计不良"。