我注意到,当我在做TDD时,它经常导致非常大量的接口。对于具有依赖关系的类,它们通过构造函数以通常的方式注入:
public class SomeClass
{
public SomeClass(IDependencyA first, IDependency second)
{
// ...
}
}
结果是几乎每个类都会实现一个接口。
是的,代码将被解耦,并且可以非常容易地进行单独测试,但也会有额外的间接级别,这让我感到有点......不安。感觉不对劲。
任何人都可以分享其他不涉及如此大量使用接口的方法吗?
你们其他人怎么样?
答案 0 :(得分:12)
您的测试告诉您重新设计课程。
有些时候,您无法避免传递需要存根的复杂协作者以使您的类可测试,但您应该寻找方法为这些协作者提供输出而不是考虑如何重新安排他们的交互以消除复杂的依赖关系。
例如,不是提供TaxCalculator
ITaxRateRepository
(在CalculateTaxes
期间访问数据库),而是在创建TaxCalculator
实例之前获取这些值并提供它们到它的构造函数:
// Bad! (If necessary on occasion)
public TaxCalculator(ITaxRateRepository taxRateRepository) {}
// Good!
public TaxCalculator(IDictonary<Locale, TaxRate> taxRateDictionary) {}
有时这意味着你必须做出更大的改变,调整对象的生命周期或重组大量的代码,但是一旦我开始寻找它,我经常会发现低洼的果实。
有关减少对依赖项依赖性的优秀技术综述,请参阅Mock Eliminating Patterns。
答案 1 :(得分:5)
不要使用接口!大多数模拟框架都可以模拟具体的类。
答案 2 :(得分:3)
这是基于模拟的测试方法的缺点。这是一个关于模拟的测试边界讨论。通过将测试用例与域类的比例为1:1,您的测试边界非常小。小测试边界的结果是依赖于它们的接口和测试的激增。由于您正在嘲笑和剔除的交互次数,重构变得更加困难。通过使用单个测试来测试类的集群,重构变得更容易,并且您使用更少的接口。但要注意,您可以一次测试太多的类。您的类越复杂,您需要测试的代码路径就越多。这可能导致组合爆炸,你无法测试它们。听取代码和测试,他们会告诉你一些你的代码。如果您看到复杂性增加,那么可能是引入新的测试用例和接口/实现并在原始模型中进行模拟的好时机。
答案 3 :(得分:1)
如果您对传递到特定类的接口数量感到不安;那么这可能表明你引入了太多不同的依赖关系。
如果 SomeClass 依赖于 IDependencyA , IDependencyB 和 IDependencyC ,这是一个机会,看看你是否可以将这三个接口所执行的逻辑提取到另一个类/接口 IDependencyABC 。
然后,当您为 SomeClass 编写测试时,您只需要模拟 IDependencyABC 现在提供的逻辑。
另外,如果你仍然不舒服;也许它不是你需要的界面。例如,包含state(例如,传递参数)的类可能只是作为具体类创建和传递。杰夫的回答暗示了这一点,他提到只需要你的构造函数。这样可以减少构造之间的耦合,更好地表明了类的需求。小心传递数据结构(IDictionary&lt;,&gt;)。
最后,当您在周期中获得温暖的模糊感时,TDD正在工作。如果您感到不安,请注意一些代码气味,并解决其中一些问题,以便重新回到正轨。