渐进式依赖注入方法


12

我正在使用依赖注入使我的课程可单元测试。但是其中一些类有很多客户端,而且我还没有准备好重构所有这些类以开始传递依赖项。所以我正在努力逐步做到;暂时保留默认依赖关系,但允许对其进行覆盖以进行测试。

我正在考虑的一种方法是将所有“新”调用移入它们自己的方法,例如:

public MyObject createMyObject(args) {
  return new MyObject(args);
}

然后,在我的单元测试中,我可以继承该类的子类,并覆盖create函数,以便由它们创建伪造的对象。

这是一个好方法吗?有什么缺点吗?

更一般而言,只要您可以替换它们以进行测试,就可以使用硬编码的依赖项吗?我知道首选方法是在构造函数中明确要求它们,而我希望最终到达那里。但是我想知道这是否是一个好的第一步。

我刚想到的一个缺点是:如果您有需要测试的真实子类,则无法重用为父类编写的测试子类。您必须为每个真实的子类创建一个测试子类,并且它必须覆盖相同的create函数。


您能否详细说明为什么它们现在不能进行单元测试?
奥斯汀·萨洛宁

在整个代码中散布着许多“新X”,以及静态类和单例的许多用法。因此,当我尝试测试一堂课时,我实际上是在测试一整堂课。如果我可以隔离依赖关系并将其替换为伪造的对象,则可以更好地控制测试。
JW01 2011年

Answers:


13

这是入门的好方法。请注意,最重要的是使用单元测试覆盖现有代码。一旦进行了测试,就可以更自由地进行重构以进一步改进设计。

因此,最初的目的不是使设计优雅而有光泽-只是使代码单元可以以最小的风险更改进行测试。如果没有单元测试,则必须格外保守和谨慎,以免破坏代码。在某些情况下,这些初始更改甚至可能使代码看起来更笨拙或丑陋-但是,如果它们使您能够编写第一个单元测试,则最终您将能够重构为您所想到的理想设计。

关于此主题的基本工作是有效地使用旧版代码。它描述了您使用多种语言在上面展示的技巧,以及许多其他更多技巧。


只需在“一旦有了测试,就可以更自由地重构”这一点上做一句话-如果没有测试,就没有重构,只是在更改代码。
ocodo 2011年

@Slomojo我明白你的意思,但是你仍然可以在没有测试的情况下进行很多安全的重构,尤其是自动化的IDE手段,例如(在Java的eclipse中)将重复的代码提取到方法中,重命名所有内容,移动类并提取字段,常量等
Alb

我真的只是在引用“重构”一词的由来,它是将代码修改为改进的模式,该模式可以通过测试框架防止回归错误。当然,基于IDE的重构使它比“重构”软件更为简单,但如果我的软件没有测试,而我“重构”我只是在更改代码,我倾向于避免自我欺骗。当然,那可能不是我告诉别人的内容;)
ocodo 2011年

1
@Alb,未经测试的重构总是比较危险。自动重构肯定会降低这种风险,但不会降低到零。总是有些棘手的情况,这些工具可能无法正确处理。在更好的情况下,结果是该工具发出了一些错误消息,但在最坏的情况下,它可能会悄悄地引入细微的错误。因此,建议即使使用自动化工具也要保持最简单,最安全的更改。
彼得Török

3

Guice民间推荐使用

@Inject
public void setX(.... X x) {
   this.x = x;
}

所有属性,而不仅仅是在其定义中添加@Inject。这将使您可以将它们视为普通bean,可以new在测试(和手动设置属性)和在生产中使用@Inject时使用。


3

当我遇到此类问题时,我将识别要测试的类的每个依赖项,并创建一个受保护的构造函数,该构造函数将允许我将它们传递给我。这实际上与为每个类创建一个setter相同,但是我更喜欢在我的构造函数中声明依赖关系,这样我就不会忘记在将来实例化它们。

因此,我的新单元可测试类将具有2个构造函数:

public MyClass() { 
  this(new Client1(), new Client2()); 
}

public MyClass(Client1 client1, Client2 client2) { 
  this.client1 = client1; 
  this.client2 = client2; 
}

2

在可以使用注入的依赖项之前,您可能要考虑“ getter创建”惯用语。

例如,

public class Example {
  private MyObject myObject=null;

  public MyObject getMyObject() {
    if (myObject==null) {
      myObject=new MyObject();
    } 
    return myObject;
  }

  public void setMyObject(MyObject myObject) {
    this.myObject=myObject;
  }

  private void myMethod() {
    if (getMyObject().doSomething()) {
      // Instance automatically created
    }
  }
}

这将允许您在内部进行重构,以便可以通过getter引用myObject。您无需致电new。将来,当所有客户端都配置为允许依赖项注入时,您要做的就是将创建代码删除到一个位置-吸气剂。设置器将允许您根据需要注入模拟对象。

上面的代码仅是一个示例,它确实直接公开内部状态。您应该仔细考虑这种方法是否适合您的代码库。

我还建议您阅读“ 有效地使用旧版代码 ”,其中包含许多有用的技巧,可以使重大重构成功。


谢谢。在某些情况下,我正在考虑采用这种方法。但是,当MyObject的构造函数需要只能从类内部使用的参数时,该怎么办?还是在您的课程生命周期中需要创建多个MyObject时?您是否必须注入MyObjectFactory?
JW01 2011年

@ JW01您可以将getter创建者代码配置为从您的类中读取内部状态并使其适合。在整个生命周期中创建多个实例将很难进行协调。如果遇到这种情况,建议您重新设计,而不要变通。不要让您的吸气剂太复杂,否则它们会成为您脖子上的磨石。您的目标是简化重构过程。如果看起来太困难,请移至其他区域进行修复。
加里·罗

那是一个单身人士。只是不要使用单例O_O
CoffeDeveloper

@DarioOO实际上这是一个非常糟糕的单身人士。根据Josh Bloch的说法,更好的实现是使用枚举。单例是有效的设计模式,并有其用途(昂贵的一次性创建等)。
加里·罗
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.