依赖注入如何促进可测试性

时间:2011-12-20 15:50:08

标签: java unit-testing dependency-injection junit factory-pattern

我一直在阅读Factory模式,并且发现了一些文章,建议将Factory模式与依赖注入结合使用,以最大限度地提高可重用性和可测试性。虽然我还没有找到这个Factory-DI混合的任何具体例子,但我将尝试并提供我的解释的一些代码示例。但是,我的问题实际上是关于这种方法如何提高可测试性。

我的解释:

所以我们有一个Widget类:

public class Widget {
    // blah
}

我们希望包含一个WidgetFactory来控制Widget的构建:

public interface WidgetFactory {

    public abstract static Widget getWidget();
}

public class StandardWidgetFactory implements WidgetFactory {

    @Override
    public final static Widget getWidget() {
        // Creates normal Widgets
    }
}

public class TestWidgetFactory implements WidgetFactory {

    @Override
    public final static Widget getWidget() {
        // Creates test/mock Widgets for unit testing purposes
    }
}

虽然这个例子使用的是Spring DI(这是我遇到的唯一API),但是如果我们谈论的是Guice或任何其他的IoC框架并不重要;这里的想法是,我们现在要在运行时注入正确的WidgetFactory实现,这取决于我们是在测试代码还是正常运行。在Spring中,bean配置可能如下所示:

<bean id="widget-factory" class="org.me.myproject.StandardWidgetFactory"/>
<bean id="test-widget-factory" class="org.me.myproject.TestWidgetFactory"/>

<bean id="injected-factory" ref="${valueWillBeStdOrTestDependingOnEnvProp}"/>

然后,在代码中:

WidgetFactory wf = applicationContext.getBean("injected-factory");
Widget w = wf.getWidget();

这样,环境(部署级别)变量(可能在某个 .properties 文件中定义)决定Spring DI是否会注入StandardWidgetFactoryTestWidgetFactory

我做得对吗?!这似乎是一个非常多的基础架构,只为我的Widget获得了良好的可测试性。并不是说我反对它,但对我来说只是过度工程化。

我的挂断:

我问这个的原因是因为我在其他包中有其他对象,其中包含在其中使用Widget个对象的方法。也许是这样的事情:

public class Fizz {
    public void doSomething() {

        WidgetFactory wf = applicationContext.getBean("injected-factory");
        Widget widget = wf.getWidget();

        int foo = widget.calculatePremable(this.rippleFactor);

        doSomethingElse(foo);
    }
}

如果没有这个看似过度设计的巨大设置,我就无法将{mock Widgets“注入Fizz::doSomething()的单元测试中。

所以我被撕裂了:一方面,我觉得我正在过度思考 - 我可能正在做的事情(如果我的解释不正确)。另一方面,我没有看到任何干净的方法来解决它。

作为一个切入问题的问题,这也引起了我的另一个巨大关注:如果我的解释是正确的(甚至有些正确),那么这是否意味着我们需要Factories每个对象?!< / p>

听起来像方式过度工程!什么是截止?什么是沙子中的线条划分何时使用工厂,什么时候不用?!感谢您对冗长问题的任何帮助和道歉。它只是让我头晕目眩。

6 个答案:

答案 0 :(得分:6)

DI / IoC有助于测试,因为您可以轻松地决定使用哪种实现,而无需修改使用它的代码。这意味着您可以注入一个已知的实现来执行特定功能,例如,模拟Web服务故障,保证对函数的良好(或不良)输入等。

工厂不需要使DI / IoC工作。是否需要工厂完全取决于使用细节。

public class Fizz {

    @Inject    // Guice, new JEE, etc. or
    @Autowired // Spring, or
    private Widget widget;

    public void doSomething() {
        int foo = widget.calculatePremable(this.rippleFactor);
        doSomethingElse(foo);
    }

}

答案 1 :(得分:2)

在你的Fizz示例类中,它是一个DI反模式,可以显式调用DI框架来获取你想要的bean。 DI的重点是好莱坞原则(不要打电话给我们,我们会打电话给你)。所以你的DI框架应该将一个Widget注入Fizz。

在测试方面,我的偏好是

  • 测试夹具创建依赖项的存根或模拟。或者是适当的时候。
  • 测试夹具使用相同的构造函数或setter方法注入存根,在生产中,我的DI框架将使用该方法。

有些人喜欢在他们的DI容器中运行测试,但我没有看到单元测试中的重点 - 它只是创建了一大堆配置来维护。 (对于集成测试,这是值得的,但在生产和集成测试上下文之间,您仍应尽可能多地使用DI上下文。)

所有这一切的结论是我没有看到对工厂模式的大量使用。

您能给我们一个您正在阅读的文章的链接吗?我会看看自己能不能挖出一两个。

编辑:这是承诺的链接:Is Dependency Injection Replacing the Factory Patterns?,是关于DI的一系列相当好的文章中的一个。

答案 2 :(得分:2)

我认为这个问题的答案很简单:使用普通的java可以实现依赖注入的好处,但代价是增加了大量的样板。

DI允许您对类进行解耦和模块化,而无需在源代码库中添加一堆参数化构造函数/工厂方法等。

答案 3 :(得分:0)

您有一个工厂,但您没有在Fizz类中使用依赖注入。这就是为什么你没有轻松测试它的原因。您需要直接注入applicationContext或WidgetFactory。在大多数情况下,我更喜欢后者。

public class Fizz {
    private WidgetFactory wf;
    public Fizz(WidgetFactory widgetFactory) {
        wf = widgetFactory;
    }

    public void doSomething() {

        Widget widget = wf.getWidget();

        int foo = widget.calculatePremable(this.rippleFactor);

        doSomethingElse(foo);
    }
}

现在你有了这个,你可以将模拟对象注入你的Fizz类。在下面的示例中,我使用Mockito,但您可以使用其他模拟框架(或者如果您真的需要,可以自己创建模拟)。

@Test
public void test() {
    WidgetFactory wf = mock(WidgetFactory.class); // mock is a Mockito function that creates mocks for you automatically.
    Fizz objectUnderTest = new Fizz(wf);

    // Test Fizz
}

在您的单元测试中,您真的不想加载applicationContext。这就是为什么你在代码中模拟它然后直接传递它。因此,您将只有一个简单的applicationContext和生产。

<bean id="widget-factory" class="org.me.myproject.StandardWidgetFactory"/>
<bean id="fizz" class="org.me.myproject.Fizz">
    <constructor-arg ref="widget-factory"/>
</bean>

关于你对一切需要工厂的问题,不,你不需要工厂的一切。对于我的类使用但不创建的对象,我更喜欢使用DI并通过构造函数传递它。如果它是可选的,你也可以通过setter传入它。如果类确实创建了一个新对象,那么工厂可能是合适的。当您开始学习如何使用DI,工厂和编写可测试代码时,我认为您将能够了解何时编写工厂太多。

答案 4 :(得分:0)

依赖注入肯定会使代码更易于测试。当我来到使用Dependency注入的未经测试的EJB3代码时,它就像是在测试其他类型的遗留代码时的昼夜。

至于工厂,他们可以有两个角色。一个是没有依赖注入框架时的依赖注入。这是一个静态的工厂模式,在effective Java中提到,这实际上是Java的独特之处,最好是出于其他原因(详见那里)。你在JDK中看到了一些,它是一个用于测试的熊,当然也不能自然地制作可测试的代码,因为它是你必须非常有意识地使用的东西。

在依赖注入中使用Gang of Four真实模式的工厂的另一个角色是实际对象的构造在某种程度上涉及,而不是简单的构造函数。在这种情况下,您注入一个工厂,可以在一个简单的界面中表示,然后让工厂实现完成繁重的工作。这更多地是围绕编程原理构建的,没有任何问题无法用另一层间接解决。

实际上,直到我开始进行Guice依赖注入,才发现需要使用Java中的GOF工厂模式。

答案 5 :(得分:0)

DI使您无需使用工厂即可编程到接口。对接口进行编程以及在类之外传递具体实现形式的能力使代码更容易测试。

interface Widget {
}

class UserOfWidget {
   Widget widget;
   public UserOfWidget(Widget widget) {
      this.widget = widget
   }
}

Using spring dependency injection
<bean id="widget" class="ConcreteWidget"/>
<bean class="UserOfWidget">
   <constructor-arg ref="widget"/>
</bean>

In a test case
UserOfWidget uow = new UserOfWidget(new StubWidget()); 

如果没有DI来实现同样的目的,你需要隐藏工厂后面的具体Widget的创建 - 类似于你的例子中的混乱代码。

即使您正在使用DI,也有使用工厂的情况。主要用于对象构造复杂的场景 - 需要多个步骤,不能通过简单的构造函数调用来完成。一个这样的例子是从JNDI(例如数据源)检索对象。从JNDI查找对象的所有逻辑都封装在一个工厂中 - 然后可以在XML文件中将其配置为一个简单的bean。

总结一下 - 您不需要带有DI的工厂来使代码更易于测试。只要DI就足够了。工厂只需要简化创建复杂对象的创建。

相关问题