在UnitTesting之外值得依赖注入吗


17

给定一个构造函数,它永远不必使用初始化它的几个对象的任何不同实现,使用DI仍然可行吗?毕竟,我们可能仍要进行单元测试。

有问题的类在其构造函数中初始化了其他一些类,并且使用的类非常特定。它永远不会使用其他实现。我们有理由避免尝试对接口进行编程吗?



3
-永远是最好的方法。
Reactgular 2013年

5
我认为这个问题比拟议的重复问题更为具体。设计未来的变化或解决手头的问题,因此,我投票决定不结束这一问题
k3b 2013年

1
可测试性还不够吗?
ConditionRacer

听起来并不一定像回答自己一样多。如果它永远不会发生,那么为它设计并不是为未来的变化而设计。即使是建筑宇航员,也只能针对错误判断而设计的更改。
Steve314 2013年

Answers:


13

这取决于您对“从不,从不”正确性的肯定程度(在使用您的应用程序期间)。

一般来说:

  • 不要仅仅为了可测试性而修改您的设计。单元测试可以为您服务,反之亦然。
  • 不要重复自己。建立一个只有一个继承者的接口是没有用的。
  • 如果一个类是不可测试的,那么它对于实际用例也不可能是灵活的/不可扩展的。有时候很好-您不太需要这种灵活性,但简单性/可靠性/可维护性/性能/更重要。
  • 如果您不知道,请对该界面进行编码。需求变更。

12
几乎给您-1的意思是“不要仅仅为了可测试性而修改您的设计”。获得可测试性是恕我直言,更改设计的首要原因之一!但是您的其他观点还可以。
布朗

5
@docBrown-(和其他)我在这里确实与许多人,甚至多数人不同。约95%的时间使事物可测试也使它们变得灵活或可扩展。你应该在你的设计中(或添加到您的设计),大部分的时间-可测性被定罪。其余的5%的需求要么很奇怪,要么就阻止了这种灵活性,而另一些东西却无法满足您的需求;或者是至关重要的/不变的,即使没有专门的测试,如果损坏,您也会很快发现。
Telastyn 2013年

4
也许可以肯定,但是您的上述第一句话很容易被误解为“当您唯一关心的是可测试性时,保留设计不良的代码”。
布朗

1
为什么会说“使一个只有一个继承者的接口是没有用的”?接口可以作为合同。
kaptan

2
@kaptan-因为具体对象可以像合同一样工作。只做两个就意味着您需要编写两次代码(并在代码更改时对其进行两次修改)。当然,绝大多数接口将具有多个(非测试)实现,或者您需要为这种情况做好准备。但是,在极少数情况下,您需要的只是一个简单的密封对象,只需直接使用它即可。
Telastyn 2013年

12

我们有理由避免尝试对接口进行编程吗?

尽管针对接口进行编码的优势非常明显,但是您应该问自己,如果不这样做,将会得到什么。

下一个问题是:有关类选择实现的责任是其一部分吗?事实可能并非如此,您应该采取相应的行动。

最终,总会有一些愚蠢的约束突然出现。您可能希望同时运行同一段代码,并且注入实现的可能性可能有助于同步。或者,在对应用程序进行性能分析之后,您可能会发现您想要一种与普通实例化不同的分配策略。否则就会出现跨领域的担忧,而您手头就没有AOP支持。谁知道?

YAGNI建议在编写代码时,一定不要添加多余的东西。程序员倾向于在甚至不知道的情况下添加到其代码中的一件事就是多余的假设。就像“此方法可能派上用场”或“此方法永远不会改变”。两者都会使您的设计混乱。


2
+1这里援引YAGNI作为参数进行 DI,不反对。
布朗

4
@DocBrown:考虑到可以在此处使用YAGNI,也可以证明使用DI。我认为这更多是反对YAGNI是对任何事情都有用的措施。
史蒂文·埃弗斯

1
+1包含在YAGNI中的多余假设,这些假设已经使我们
大吃一惊

@SteveEvers:我认为使用YAGNI来证明忽略依赖关系反转原理是可以避免对YAGNI或DIP甚至某些编程和推理的更基本原理的理解。仅在需要时才应忽略DIP- 在这种情况下YAGNI根本就不适用。
back2dos

@ back2dos:由于“可能需要”和“谁知道”,DI很有用。正是YAGNI旨在打击的思路,相反,您只是将其用作其他工具和基础设施的理由。
史蒂文·埃弗斯

7

它取决于各种参数,但是有一些您仍要使用DI的原因:

  • 我相信测试本身就是一个很好的理由
  • 对象所需的类可能会在其他地方使用-如果注入它们,则可以使资源池化(例如,如果所涉及的类是线程或数据库连接-您不希望每个类都创建新的连接)
  • 如果初始化那些其他对象可能会失败,那么与您的类相比,更高级别的代码可能会更好地处理问题,而类可能会简单地转发错误

底线:在这种情况下,您可能无需使用DI,但是使用DI最终可能会得到更清晰的代码。


3

在设计程序或功能时,思考过程的一部分应该是如何对其进行测试。依赖注入是测试工具之一,应被视为组合的一部分。

扩展应用程序时,依赖注入和使用接口代替类的其他好处在扩展应用程序时也将受益,但是以后还可以使用搜索和替换将它们很容易且安全地改装到源代码中。-即使在大型项目中。


2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

如果您不想进行单元测试,则可以将这个问题的许多答案辩论为“您可能需要,因为..”,例如YAGNI。

如果您要问除单元测试外还有什么原因需要对接口进行编程

是的,如果需要反转控制, UnitTesting 之外值得进行依赖注入。例如,如果一个模块实现需要在其层中不可访问的另一模块。

示例:如果您有模块gui=> businesslayer=> driver=> common

在这种情况下businesslayer可以使用driverdriver不能使用businesslayer

如果driver需要一些更高级别的功能,则可以实现此功能,businesslayer其中可以在common层中实现接口。 driver只需要知道common层中的接口。


1

是的,您是-首先,忘记单元测试是围绕单元测试工具设计代码的原因,弯曲代码设计以适应人为约束永远不是一个好主意。如果您的工具强迫您执行此操作,请使用更好的工具(例如,Microsoft Fakes / Moles,它为您提供了更多创建模拟对象的选项)。

例如,是否仅因为测试工具不能与私有方法一起使用而将您的类拆分为仅公共方法?(我知道流行的智慧是假装您不需要测试私有方法,但是我认为这是对使用当前工具进行测试的困难的一种反应,而不是对不需要测试私有方法的真正反应)。

总而言之,这取决于您是哪种TDDer- Fowler所描述的“模拟主义者” ,需要更改代码以适合他们使用的工具,而“经典”测试人员所创建的测试本质上是更集成的(例如,将类作为一个单元而不是每个方法进行测试),因此对接口的需求减少,尤其是在使用可以模拟具体类的工具的情况下。


3
我认为,不应该测试私有方法的普遍观点与测试它们的难度无关。如果私有方法中存在一个错误,但是它没有影响对象的行为,那么它就不会影响任何事情。如果私有方法中的某个错误确实影响了对象的行为,则对对象的至少一个公共方法进行的测试是否失败。
萧伯纳

这取决于您是否测试行为或状态。显然,私有方法需要以某种方式进行测试,但是,如果您是传统的TDD人员,则可以通过“整体”测试类来测试它们,大多数人使用的测试工具都集中在测试每种方法上单独...我的意思是,后者实际上没有正确进行测试,因为他们错过了执行完整测试的机会。因此,我同意您的观点,同时试图强调当今(部分?)人们进行单元测试的方式如何颠覆了TDD。
gbjbaanb

1

依赖注入还使您的对象具有HAS依赖关系更加清楚。

当其他人天真地使用您的对象(无需查看构造函数)时,他们会发现他们需要设置服务,连接等。这并不明显,因为他们不必将它们传递给构造函数。传递依赖项可以使需求更加清晰。

您还可能隐藏了您的课程可能违反SRP的事实。如果您传递了许多依赖关系(超过3个),则您的类可能做得太多,应该对其进行重构。但是,如果您在构造函数中创建它们,则此代码气味将被隐藏。


0

我同意这里的两个阵营,我认为此事仍在辩论中。YAGNI要求我不要处理更改代码以适应假设的情况。这里的单元测试不是问题,因为我坚信依赖关系永远不会改变。但是,如果我一开始就打算进行单元测试,那么我永远都不会达到这一点。就像我最终进入DI的道路一样。

让我为我的情况提供另一种选择

使用工厂模式,我可以将依赖关系集中在一个地方。在测试时,我可以根据上下文更改实现,这已经足够了。这与YAGNI并不矛盾,因为抽象了几个类构造的好处。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.