如何处理TDD中的接口过度使用?

我注意到,当我在做TDD时,它经常导致非常大量的接口。 对于具有依赖项的类,它们以通常的方式通过构造函数注入:

public class SomeClass { public SomeClass(IDependencyA first, IDependency second) { // ... } } 

结果是几乎每个类都将实现一个接口。

是的,代码将被解耦并且可以非常容易地进行单独测试,但是也会有额外的间接级别让我感到有点……不安。 有些事情感觉不对劲。

任何人都可以分享其他不涉及如此大量使用接口的方法吗?

你们其他人怎么样?

您的测试告诉您重新设计课程。

有时您无法避免传递需要存根的复杂协作者以使您的类可测试,但您应该寻找方法为他们提供这些协作者的输出 ,并考虑如何重新安排他们的交互消除复杂的依赖关系。

例如,不是在TaxCalculator提供ITaxRateRepository (在CalculateTaxes期间命中数据库), TaxCalculator在创建TaxCalculator实例之前获取这些值并将它们提供给它的构造函数:

 // Bad! (If necessary on occasion) public TaxCalculator(ITaxRateRepository taxRateRepository) {} // Good! public TaxCalculator(IDictonary taxRateDictionary) {} 

有时这意味着你必须做出更大的改变,调整对象的生命周期或重组大量的代码,但是一旦我开始寻找它,我经常会发现低洼的果实。

有关减少对依赖项依赖性的优秀技术综述,请参阅Mock Eliminating Patterns 。

不要使用接口! 大多数模拟框架都可以模拟具体类。

这是基于模拟的测试方法的缺点。 这是一个关于模拟的测试边界讨论。 通过将测试用例与域类的比率设置为1:1,您的测试边界非常小。 小测试边界的结果是依赖于它们的接口和测试的激增。 由于您正在嘲笑和剔除的交互次数,重构变得更加困难。 通过使用单个测试来测试类的集群,重构变得更容易,并且您使用更少的接口。 但要注意,您可以一次测试太多的类。 您的类越复杂,您需要测试的代码路径就越多。 这可能导致组合爆炸,你无法测试它们。 听取代码和测试,他们会告诉你一些你的代码。 如果您看到复杂性增加,那么可能是引入新的测试用例和接口/实现并在原始模型中进行模拟的好时机​​。

如果您对传递到特定类的接口数量感到不安; 那么这可能表明你引入了太多不同的依赖关系。

如果SomeClass依赖于IDependencyAIDependencyBIDependencyC ,那么这是一个机会,看看你是否可以将这三个接口所执行的逻辑提取到另一个类/接口IDependencyABC中

然后,当您为SomeClass编写测试时,您只需要模拟IDependencyABC现在提供的逻辑。

另外,如果你还是不舒服; 也许它不是你需要的界面。 例如,包含状态的类(例如,传递参数)可能只是创建并作为具体类传递。 杰夫的回答提到了这一点,他提到只需要你的构造函数。 这样可以减少构造之间的耦合,更好地表明了类的需求。 小心传递数据结构(IDictionary <,>)。

最后,当您在周期中获得温暖的模糊感时,TDD正在工作。 如果您感到不安,请注意一些代码气味并解决其中一些问题以便重新回到正轨。