自从学习(并喜欢)自动化测试以来,我发现自己几乎在每个项目中都使用了依赖注入模式。在进行自动化测试时,使用这种模式是否总是合适的?您是否应该避免使用依赖注入?
自从学习(并喜欢)自动化测试以来,我发现自己几乎在每个项目中都使用了依赖注入模式。在进行自动化测试时,使用这种模式是否总是合适的?您是否应该避免使用依赖注入?
Answers:
基本上,依赖注入会对对象的性质做出一些(通常但并非总是有效的)假设。如果错误,那么DI可能不是最佳解决方案:
首先,最重要的是,DI假设对象实现的紧密结合总是不好的。这就是依赖倒置原则的本质:“从不应该依赖于具体的东西,而不能依赖抽象”。
这将根据具体实现的更改关闭要更改的从属对象;如果需要将输出转至文件,则具体取决于ConsoleWriter的类将需要更改,但是,如果该类仅依赖于暴露Write()方法的IWriter,则可以用FileWriter替换当前使用的ConsoleWriter,并且依赖类不会知道区别(Liskhov替代原理)。
但是,设计永远都不可能对所有类型的变更都是封闭的。如果IWriter接口本身的设计发生更改,则要向Write()添加参数,则必须在实现对象/方法及其用法的基础上更改一个额外的代码对象(IWriter接口)。如果实际接口中的更改比对所述接口的实现进行更改的可能性更大,那么松散耦合(和DI-ing松散耦合的依赖项)可能会导致更多问题,而不是解决的问题。
其次,推论是,DI假定从属类永远不是创建依赖的好地方。这遵循单一责任原则;如果您具有创建依赖项并也使用它的代码,则依赖项类可能不得不更改(更改用法或实现)有两个原因,这违反了SRP。
但是,再次,为DI添加间接层可以解决不存在的问题。如果将逻辑封装在依赖关系中是逻辑上的,但是该逻辑是依赖关系的唯一这样的实现,那么编写依赖关系的松耦合解析(注入,服务位置,工厂)将比编写代码更痛苦。只是使用new
而忘记它。
最后,DI的本质是集中所有依赖及其实现的知识。这会增加执行注入的程序集必须具有的引用数,并且在大多数情况下不会减少实际依赖类的程序集所需的引用数。
SOMETHING,SOMEWHERE,必须具有相关性,相关性接口和相关性实现的知识,才能“连接点”并满足该相关性。DI倾向于将所有这些知识放在一个非常高的层次上,或者放在IoC容器中,或者放在创建“主要”对象的代码中,例如必须混合(或提供工厂方法)依赖项的“主要”对象或主窗体。这可以在应用程序的高层放置大量必须紧密耦合的代码和大量程序集引用,而这仅需要这些知识即可将其从实际的依赖类中“隐藏”(从非常基本的角度来看,了解这些知识的最佳地点;使用地点)。
通常,它也不会从代码的最下面移除引用。一个依赖项仍然必须引用包含该接口的接口的依赖项,该库位于以下三个位置之一:
所有这些,再次解决了可能没有的地方的问题。
在依赖关系注入框架之外,依赖关系注入(通过构造函数注入或setter注入)几乎是一个零和游戏:您减少了对象A与它的依赖关系B之间的耦合,但是现在任何需要A实例的对象都必须这样做还要构造对象B。
您已经稍微减少了A和B之间的耦合,但是减少了A的封装,并通过将A和必须构造A实例的任何类也耦合到A的依赖关系来增加了它们之间的耦合。
因此,依赖项注入(没有框架)同样有害,因为它很有帮助。
但是,额外的费用通常很容易被证明是合理的:如果客户端代码比对象本身更了解如何构造依赖项,那么依赖项注入的确会减少耦合。例如,扫描程序对如何获取或构造一个输入流来解析输入内容,或者客户代码想要从哪个源解析输入内容的知识并不多,因此构造函数注入输入流是显而易见的解决方案。
为了能够使用模拟依赖项,测试是另一个理由。那应该意味着添加一个仅用于测试的额外构造函数,该测试仅允许注入依赖关系:如果您改为将构造函数更改为始终要求注入依赖关系,突然之间,您必须了解依赖关系的依赖关系才能构建您的依赖关系直接依赖关系,您将无法完成任何工作。
它可能会有所帮助,但是您一定要问问自己每个依赖项,测试的好处值得吗?我真的要在测试时模拟这个依赖项吗?
当添加依赖项注入框架,并且依赖项的构造不是委托给客户端代码而是委托给框架时,成本/收益分析将发生很大变化。
在依赖注入框架中,权衡有所不同。通过注入依赖项而失去的是能够轻松了解您所依赖的实现,并将决定所依赖的依赖项的职责转移到一些自动解决程序上的能力(例如,如果我们需要@ Inject'ed Foo ,必须有@Provides Foo的东西,并且注入的依赖项可用),或者必须有一些高级配置文件,该文件规定应为每种资源使用哪种提供程序,或者两者的某种混合(例如,可能存在是针对依赖项的自动解析过程,可以在需要时使用配置文件将其覆盖)。
就像在构造函数注入中一样,我认为这样做的好处再次与这样做的成本非常相似:您不必知道谁在提供您所依赖的数据,以及是否存在多重潜力提供者,您不必知道检查提供者的首选顺序,确保每个需要数据的位置都需要检查所有潜在的提供者,依此类推,因为所有这些都由依赖项注入在高层处理平台。
虽然我个人没有大量的DI框架经验,但我的印象是,当寻找所需的正确数据或服务提供者的头痛比头痛时要付出更高的代价时,它们提供的好处多于成本。当某件事失败时,不能立即本地知道哪些代码提供了导致以后代码失败的错误数据。
在某些情况下,当DI框架出现时,已经采用了其他掩盖依赖性的模式(例如,服务定位符)(也许还证明了它们的价值),之所以采用DI框架是因为它们提供了一些竞争优势,例如要求更少的样板代码,或者在有必要确定实际使用的提供者时可能做得更少,从而使依赖提供者变得晦涩难懂。
如果要创建数据库实体,则应该有一些工厂类,将其注入到控制器中,
如果您需要创建int或longs之类的原始对象。另外,您应该“手动”创建大多数标准库对象,例如日期,向导等。
如果要注入配置字符串,最好注入一些配置对象(通常,建议将简单类型包装到有意义的对象中:int temperatureInCelsiusDegrees-> CelciusDeegree temperature)
并且不要将Service locator用作依赖项注入的替代方法,它是一种反模式,更多信息:http : //blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
当您无法通过使项目可维护和可测试而获得任何收益时。
认真地说,我总体上喜欢IoC和DI,我想说98%的时间我都会使用该模式而不会失败。这在多用户环境中尤其重要,因为您的代码将逻辑与实现分开,因此您的代码可以由不同的团队成员和不同的项目反复使用。日志记录就是一个很好的例子,一个类的ILog接口比简单地插入您的日志记录框架du-jour的可维护性高一千倍,因为您不能保证另一个项目将使用相同的日志记录框架(如果使用一个!
但是,有时它不是适用的模式。例如,由不可重写的初始化程序在静态上下文中实现的功能入口点(WebMethods,我在看您,但是Program类中的Main()方法是另一个示例),根本无法在初始化时注入依赖项时间。我还要说原型或任何扔掉的调查性代码也是不好的选择。DI的好处几乎是中长期的好处(可测试性和可维护性),如果您确定会在一周之内扔掉大部分代码,那么我想说通过隔离您不会获得任何好处依赖关系,只需花费您通常用于测试和隔离依赖关系的时间即可使代码正常工作。
总而言之,对任何方法论或模式都采取务实的态度是明智的,因为100%的情况下都不适用。
需要注意的一件事是您对自动化测试的评论:我对此的定义是自动化功能测试,例如,如果您处于Web上下文中,则为脚本化硒测试。这些通常是完全黑盒测试,无需了解代码的内部工作原理。如果您指的是单元测试或集成测试,那么我会说,DI模式几乎总是适用于任何严重依赖这种白盒测试的项目,因为例如,它允许您测试诸如不需要数据库就可以接触数据库的方法。
其他答案集中在技术方面时,我想添加一个实际的方面。
多年来,我得出的结论是,要成功实施依赖注入,必须满足一些实际要求。
应该有理由进行介绍。
这听起来很明显,但是如果您的代码仅从数据库中获取内容并没有任何逻辑地将其返回,则添加DI容器会使事情变得更复杂而没有实际好处。集成测试在这里更重要。
该团队需要接受培训并入职。
除非团队的大多数成员都参与其中并且了解DI,否则添加控制容器的反转将成为另一种处理事情的方法,并使代码库更加复杂。
如果DI是由团队的新成员介绍的,因为他们了解并喜欢它,并且只想表明自己是优秀的,并且团队没有积极参与,则存在真正降低其质量的风险。编码。
你需要测试
虽然去耦通常是一件好事,但DI可以将依赖项的分辨率从编译时移到运行时。如果测试不好,这实际上是非常危险的。运行时解析失败可能是昂贵的跟踪和解决方案。
(从您的测试中可以很明显地看出来,但是许多团队并未按照DI的要求进行测试。)
这不是一个完整的答案,只是另一点。
如果您的应用程序只能启动一次,并且可以运行很长时间(例如Web应用程序),那么DI可能很好。
当您的应用程序启动多次且运行时间较短时(例如移动应用程序),您可能不希望使用该容器。
尝试使用基本的OOP原则:使用继承提取通用功能,封装(隐藏)应使用私有/内部/受保护的成员/类型保护的东西,以防止外界的侵害。使用任何功能强大的测试框架仅注入测试代码,例如https://www.typemock.com/或https://www.telerik.com/products/mocking.aspx。
然后尝试用DI重新编写它,并比较代码,这些通常会在DI中看到:
从我所看到的情况来看,我几乎总是说,DI导致代码质量下降。
但是,如果您在类声明中仅使用“公共”访问修饰符,和/或为成员使用了公共/私有修饰符,并且/或者您没有选择购买昂贵的测试工具,而同时又需要进行单元测试,那么不能用集成测试代替,和/或您已经拥有要注入的类的接口,DI是一个不错的选择!
ps可能我会在这篇文章中有很多缺点,我相信是因为大多数现代开发人员只是不了解如何以及为什么使用内部关键字,以及如何减少组件的耦合,最后为什么要减少它的耦合),最后,只是尝试编码和比较
依赖注入的一种替代方法是使用服务定位器。服务定位器更易于理解,调试,并且使对象的构造更简单,尤其是在您不使用DI框架的情况下。服务定位器是管理外部静态依赖项的好模式,例如,否则您必须将其传递到数据访问层中的每个对象中的数据库。
当重构遗留代码,它往往比依赖注入更容易重构为一个服务定位器。您要做的就是用服务查找替换实例化,然后在单元测试中伪造服务。
但是,服务定位器有一些缺点。知道类的关系更加困难,因为依赖关系隐藏在类的实现中,而不是构造函数或setter中。创建依赖于同一服务的不同实现的两个对象是困难或不可能的。
TLDR:如果您的类具有静态依赖关系,或者您要重构遗留代码,那么服务定位器可以说比DI更好。