Test Driven Development with Alfresco - Part 2 Inversion of Control
In my last post I briefly described the process of TDD. In that discussion I stated that a unit test will test in isolation; meaning that you don't want to test a unit's dependencies only the unit itself. In the real world our code doesn't run in isolation, in the real world our code has dependencies. I also stated that in a future post I would talk about test doubles, however before I can talk about that we need to discuss how we isolate our class from the world around it. Of course we can't isolate it completely, a completely isolated class is generally useless, but we can decrease the dependencies. This dependency on other classes is referred to as coupling and coupling is generally considered bad.
An excellent way to reduce coupling is called inversion of control, which is also referred to as dependency injection. Many people when they think of inversion of control think of frameworks such as Spring, JUICE, or Java EE. Although these frameworks offer inversion of control the concept predates all of them. Inversion of control doesn't require a fancy framework as a matter of fact the code should be agnostic of the framework whenever possible.
The class MyUnitWithDependency is completely coupled to the implementation of DependencyImpl. There is no way I could even override the dependency if I wanted. The most egregious part of this code is that I am clearly referring to an implementation of something that has an interface. I couldn't change the implementation without changing not only line 4 but also line 2. So lets improve this slightly by fixing that issue:
That's a little better. We still have a strong coupling to our implementation but at least we only have to change line 4 now. So lets invert the control here:
Now our dependency comes from outside of our class. We can create the class with any implementation of Dependency we want. We have pushed the control outside of the class. It could be a factory that creates our class, it could be another class, or it could be a container like Spring provides. The important thing isn't how it gets created but that it does and that our class isn't dependent on how it gets created. As we will explore in a future post this also allows us to easily create and use test doubles to completely decouple are tests from our dependencies.
Just to be complete here is our same code using setter style injection rather than constructor style injection:
The main difference is the constructor goes away and is replaced by a getter and setter. The getter isn't absolutely required however it should be provided at least for the sake of the tests.
More next time!
An excellent way to reduce coupling is called inversion of control, which is also referred to as dependency injection. Many people when they think of inversion of control think of frameworks such as Spring, JUICE, or Java EE. Although these frameworks offer inversion of control the concept predates all of them. Inversion of control doesn't require a fancy framework as a matter of fact the code should be agnostic of the framework whenever possible.
Inversion of Control Without a Framework
I have run into so many people who think inversion of control is synonymous with Spring. It isn't. Spring is an excellent IoC container but sometimes I feel the concept of IoC is muddied by the frameworks. It really is a simple concept. You are inverting the control of dependencies from an object obtaining the dependencies to an object receiving its dependencies. Essentially you are removing the new keyword from your code and you assume your dependencies are there when you need them. You can force this by requiring the constructor to take the dependencies but this isn't required and personally I find injection via setters to be cleaner when writing my unit tests. Let me try demonstrating this. First lets start with code that isn't using IoC:1: public class MyUnitWithDependency {
2: private final DependencyImpl myDependency;
3: public MyUnitWithDependency() {
4: myDependency = new DependencyImpl();
5: }
6: }
The class MyUnitWithDependency is completely coupled to the implementation of DependencyImpl. There is no way I could even override the dependency if I wanted. The most egregious part of this code is that I am clearly referring to an implementation of something that has an interface. I couldn't change the implementation without changing not only line 4 but also line 2. So lets improve this slightly by fixing that issue:
1: public class MyUnitWithDependency {
2: private final Dependency myDependency;
3: public MyUnitWithDependency() {
4: myDependency = new DependencyImpl();
5: }
6: }
That's a little better. We still have a strong coupling to our implementation but at least we only have to change line 4 now. So lets invert the control here:
1: public class MyUnitWithDependency {
2: private final Dependency myDependency;
3: public MyUnitWithDependency(Dependency d) {
4: myDependency = d;
5: }
6: }
Now our dependency comes from outside of our class. We can create the class with any implementation of Dependency we want. We have pushed the control outside of the class. It could be a factory that creates our class, it could be another class, or it could be a container like Spring provides. The important thing isn't how it gets created but that it does and that our class isn't dependent on how it gets created. As we will explore in a future post this also allows us to easily create and use test doubles to completely decouple are tests from our dependencies.
Just to be complete here is our same code using setter style injection rather than constructor style injection:
1: public class MyUnitWithDependency {
2: private Dependency myDependency;
3: public void setMyDependency(Dependency d) {
4: myDependency = d;
5: }
6: public Dependency getMyDependency() {
7: return myDependency;
8: }
9: }
The main difference is the constructor goes away and is replaced by a getter and setter. The getter isn't absolutely required however it should be provided at least for the sake of the tests.
More next time!
Comments
Post a Comment