How Dependency Injection Fosters Testability

不问归期 提交于 2019-12-10 13:40:36

问题


I've been reading up on the Factory pattern, and have come across articles that suggest using the Factory pattern in conjunction with dependency injection to maximize reusability and testability. Although I have not been able to find any concrete examples of this Factory-DI hybrid, I'm going to try and give some code examples of my interpretation. However, my question is really about how this approach improves testability.

My interpretation:

So we have a Widget class:

public class Widget {
    // blah
}

And we want to include a WidgetFactory to control the construction of Widgets:

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
    }
}

Although this example uses Spring DI (that's the only API I have experience with), it doesn't really matter if we're talking about Guice or any other IoC framework; the idea here is that we're now going to inject the correct WidgetFactory implementation at runtime to depending on whether we are testing the code or running normally. In Spring the beans config might look like this:

<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}"/>

Then, in the code:

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

This way, an environmental (deployment-level) variable, perhaps defined in a .properties file somewhere, decides whether Spring DI will inject a StandardWidgetFactory or a TestWidgetFactory.

Am I doing it right?!? This seems like an awful lot of infrastructure just obtain good testability for my Widget. Not that I'm opposed to it, but it just feels like over-engineering to me.

My hangup:

The reason why I am asking this is because I will have other objects in other packages, which have methods that use Widget objects inside of them. Perhaps something like:

public class Fizz {
    public void doSomething() {

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

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

        doSomethingElse(foo);
    }
}

Without this huge, seemingly-over-engineered setup, there would be no way for me to inject "mock Widgets" into my unit test for Fizz::doSomething().

So I'm torn: on one end it just feels like I'm overthinking things - which I may very well be doing (if my interpretation is incorrect). On the other hand I don't see any clean way to get around it.

As a segue into a tangential question, this also raises another huge concern of mine: if my interpretation is correct (or even somewhat correct), then does this mean we need Factories for every object?!?

That sounds like way-overengineering! What's the cut-off? What's the line in the sand that demarcates when to use a Factory, and when not to?! Thanks for any help and apologies for a verbose question. It's just got my head spinning.


回答1:


DI/IoC helps testing because you can decide, easily, what implementation to use, without modifying the code that uses it. This means you can inject a known implementation to exercise specific functionality, e.g., simulate a web service failure, guarantee good (or bad) input to a function, etc.

Factories are not required to make DI/IoC work. Whether or not a factory is required depends entirely on usage specifics.

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);
    }

}



回答2:


In your Fizz example class, it's a DI anti-pattern to make an explicit call into the DI framework to get the bean you want. The whole point of DI is the Hollywood principle (don't call us, we'll call you). So your DI framework should be injecting a Widget into Fizz.

When it comes to testing, my preference is

  • the test fixture creates a stub or mock of the dependency. Or the real thing when it's appropriate.
  • the test fixture injects the stub using the same constructor or setter method that, in production, would be used by my DI framework.

Some people like to run tests inside their DI container, but I don't see the point in this for unit tests - it just creates an extra lot of configuration to maintain. (For integration tests it's worthwhile, but you should still have as much of your DI context as possible in common between the production and integration-test contexts.)

The conclusion of all that is that I don't see a whole lot of use to the Factory pattern.

Can you give us a link to the article(s) you've been reading? I'll see if I can dig up one or two myself.

Edit: Here's the promised link: Is Dependency Injection Replacing the Factory Patterns?, one in a series of rather good articles about DI.




回答3:


I think the answer to this question, at its core, is simple : The benefits of Dependency Injection can be realized using normal java, but at the cost of adding lots of boiler plate.

DI allows you to decouple and modularize your classes without necessarily adding a bunch of parameterized constructors / Factory methods, etc to your source code base.




回答4:


Dependency injection definitely makes code more testable. When I come to untested EJB3 code that used the Dependency injection, it is like night and day compared to getting other types of legacy code under test.

As for factories, they can have two roles. One is dependency injection when you don't have a dependency injection framework. This is a static factory pattern, spoken about in effective Java, which is actually rather unique to Java, and best done for other reasons (elaborated there). You see some of that in the JDK, and it is a bear for testing, and certainly doesn't make testable code naturally, as it is something you have to be very conscious about using.

Another role for factories, where you are using the Gang of Four true pattern, in dependency injection is where the construction of the actual object is somewhat involved, not a simple constructor. In that case, you inject a factory, which can be represented in a simple interface, and then have the factory implementation do the heavy lifting. This is more built around the principle of in programming, there is no problem that cannot be solved with yet another layer of indirection.

It actually wasn't until I started doing Guice dependency injection that I found a need for the GOF factory pattern in Java.




回答5:


DI enables you to program to interfaces without having to use factories. Programming to interfaces and the ability to pass in a concrete implementation form outside of the class is what makes code easier to test.

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()); 

Without DI to achieve the same you would need to hide the creation of the concrete Widget behind a factory - similar to the messy code that you have in your example.

That said there is a case for using Factories even when you are using DI. Mostly for scenarios where the construction of the object is complex - requires multiple steps, cannot be done by just a simple constructor call. One such example is retrieving an object from JNDI (a datasource for example). All the logic to lookup the object from JNDI is encapsulated in an factory - which then can be configured as a simple bean in the XML file.

To summarize - you don't need factories with DI to make code more testable. Just DI is enough. Factories are only required to simplify creation of objects that are complex to create.




回答6:


You have a factory, but you are not using Dependency Injection in your Fizz class. That is why you are not having an easy time testing it. You need to inject either the applicationContext or the WidgetFactory directly. I prefer the latter in most cases.

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);
    }
}

Now that you have this, you can inject mock objects into your Fizz class. In the example below I use Mockito, but you can use other mocking frameworks (or make your own mock, if you really want).

@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
}

In your unit testing you really don't want to load an applicationContext. That is why you mock it in code and then pass it in directly. Thus you will have only one simple applicationContext and that for production.

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

In regard to you question about needing a factory for everything, no you don't need a factory for everything. For objects which my class uses and does not create, I prefer to use DI and pass it in through a constructor. You can also pass it in through a setter if it is optional. If the class does create a new object then a factory may be appropriate. As you begin to learn how to use DI, factories, and write testable code I think you will be able to get a feel for when it is too much to write a factory or not.



来源:https://stackoverflow.com/questions/8578104/how-dependency-injection-fosters-testability

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!