How to Think About the "new" Operator with Respect to Unit Testing
Thursday, July 10, 2008
By Miško Hevery
Unit-Testing as the name implies is testing of a Unit (most likely a Class) in isolation. Suppose you have a class House. In your JUnit test-case you simply instantiate the class House, set it to a particular state, call the method you want to test and then assert that the class' final state is what you would expect. Simple stuff really...
class House {
private boolean isLocked;
private boolean isLocked() {
return isLocked;
}
private boolean lock() {
isLocked = true;
}
}
If you look at House closely you will realize that this class is a leaf of your application. By leaf I mean that it is the end of the dependency graph, it does not reference any other classes. As such all leafs of any code base are easy to test, because the dependency graph ends with the leaf. But testing classes which are not leafs can be a problem because we may not be able to instantiate the class in isolation.
class House {
private final Kitchen kitchen = new Kitchen();
private boolean isLocked;
private boolean isLocked() {
return isLocked;
}
private boolean lock() {
kitchen.lock();
isLocked = true;
}
}
In this updated version of House it is not possible to instantiate House without the Kitchen. The reason for this is that the new operator of Kitchen is embedded within the House logic and there is nothing we can do in a test to prevent the Kitchen from getting instantiated. We say that we are mixing the concern of application instantiation with concern of application logic. In order to achieve true unit testing we need to instantiate real House with a fake Kitchen so that we can unit-test the House in isolation.
class House {
private final Kitchen kitchen;
private boolean isLocked;
public House(Kitchen kitchen) {
this.kitchen = kitchen;
}
private boolean isLocked() {
return isLocked;
}
private boolean lock() {
kitchen.lock();
isLocked = true;
}
}
Notice how we have removed the new operator from the application logic. This makes testing easy. To test we simply new-up a real House and use a mocking framework to create a fake Kitchen. This way we can still test House in isolation even if it is not a leaf of an application graph.
But where have the new operators gone? Well, we need a factory object which is responsible for instantiating the whole object graph of the application. An example of what such an object may look like is below. Notice how all of the new operators from your applications migrate here.
class ApplicationBuilder {
House build() {
return new House(
new Kitchen(new Sink(), new Dishwasher(), new Refrigerator())
);
}
}
As a result your main method simply asks the ApplicationBuilder to construct the object graph for you application and then fires of the application by calling a method which does work.
class Main {
public static void(String...args) {
House house = new ApplicationBuilder().build();
house.lock();
}
}
Asking for your dependencies instead of constructing them withing the application logic is called "Dependency Injection" and is nothing new in the unit-testing world. But the reason why Dependency Injection is so important is that within unit-tests you want to test a small subset of your application. The requirement is that you can construct that small subset of the application independently of the whole system. If you mix application logic with graph construction (the new operator) unit-testing becomes impossible for anything but the leaf nodes in your application. Without Dependency Injection the only kind of testing you can do is scenario-testing, where you instantiate the whole application and than pretend to be the user in some automated way.
- Unit Testing as the name implies asks you to test a Class (Unit) in isolation.
- If your code mixes Object Construction with Logic you will never be able to achieve isolation.
- In order to unit-test you need to separate object graph construction from the application logic into two different classes
- The end goal is to have either: classes with logic OR classes with "new" operators.
Unit-Testing as the name implies is testing of a Unit (most likely a Class) in isolation. Suppose you have a class House. In your JUnit test-case you simply instantiate the class House, set it to a particular state, call the method you want to test and then assert that the class' final state is what you would expect. Simple stuff really...
class House {
private boolean isLocked;
private boolean isLocked() {
return isLocked;
}
private boolean lock() {
isLocked = true;
}
}
If you look at House closely you will realize that this class is a leaf of your application. By leaf I mean that it is the end of the dependency graph, it does not reference any other classes. As such all leafs of any code base are easy to test, because the dependency graph ends with the leaf. But testing classes which are not leafs can be a problem because we may not be able to instantiate the class in isolation.
class House {
private final Kitchen kitchen = new Kitchen();
private boolean isLocked;
private boolean isLocked() {
return isLocked;
}
private boolean lock() {
kitchen.lock();
isLocked = true;
}
}
In this updated version of House it is not possible to instantiate House without the Kitchen. The reason for this is that the new operator of Kitchen is embedded within the House logic and there is nothing we can do in a test to prevent the Kitchen from getting instantiated. We say that we are mixing the concern of application instantiation with concern of application logic. In order to achieve true unit testing we need to instantiate real House with a fake Kitchen so that we can unit-test the House in isolation.
class House {
private final Kitchen kitchen;
private boolean isLocked;
public House(Kitchen kitchen) {
this.kitchen = kitchen;
}
private boolean isLocked() {
return isLocked;
}
private boolean lock() {
kitchen.lock();
isLocked = true;
}
}
Notice how we have removed the new operator from the application logic. This makes testing easy. To test we simply new-up a real House and use a mocking framework to create a fake Kitchen. This way we can still test House in isolation even if it is not a leaf of an application graph.
But where have the new operators gone? Well, we need a factory object which is responsible for instantiating the whole object graph of the application. An example of what such an object may look like is below. Notice how all of the new operators from your applications migrate here.
class ApplicationBuilder {
House build() {
return new House(
new Kitchen(new Sink(), new Dishwasher(), new Refrigerator())
);
}
}
As a result your main method simply asks the ApplicationBuilder to construct the object graph for you application and then fires of the application by calling a method which does work.
class Main {
public static void(String...args) {
House house = new ApplicationBuilder().build();
house.lock();
}
}
Asking for your dependencies instead of constructing them withing the application logic is called "Dependency Injection" and is nothing new in the unit-testing world. But the reason why Dependency Injection is so important is that within unit-tests you want to test a small subset of your application. The requirement is that you can construct that small subset of the application independently of the whole system. If you mix application logic with graph construction (the new operator) unit-testing becomes impossible for anything but the leaf nodes in your application. Without Dependency Injection the only kind of testing you can do is scenario-testing, where you instantiate the whole application and than pretend to be the user in some automated way.
Wow great tip; i have to learn about unit testing for my summer practice and this is an excellent starting point!
ReplyDeleteGreat post! We also worked with dependency injection to achieve better decomposition of tests. While you suggest to change the application, we took the approach of changing the testing framework. We separate unit under test construction from the test logic! But I think both approaches can work together very nice. Let me give an example:
ReplyDelete@Test
public Kitchen testKitchen() {
... create some kitchen and test it...
return kitchen;
}
@Test
@Depends("testKitchen")
public House testHouse(Kitchen kitchen) {
... use the kitchen to create a house and test it...
return house;
}
We extended JUnit with explicit depedencies (@Depends) and use return values and parameters to inject the result of one test into the next test. If a prerequisite fails, all dependees are skipped.
We call our approach "Example Driven", because (other than eg mocks) our objects are actually real example instances of the unit under test. And each test method is an example of how to use it.
More see JExample
cheers,
AA
Ah, but you just don't have this problem with Python. Dynamic languages (in general) are *so* much easier to test.
ReplyDelete"In this updated version of House it is not possible to instantiate House without the Kitchen."
In Python this statement is simply not true.
You patch the name 'Kitchen' at the module level, and house will use the patched Kitchen rather than the original.
@fuzzyman: Javascript too supports gorilla (nee guerilla) patching.
ReplyDeleteIMHO Dependency Injection sets my code up for better design as I am freed to use interfaces more throughout my code.
I prefer dependency injection to gorilla patching as I'm freed to declare behavior inside the testing class (where I want it) instead of creating a bogus unit that mocks something else.
@Josh
ReplyDeleteWe (Resolver Systems) use a combination of both - although we probably do a bit too much mocking in unit tests.
I often find that dependency injection just adds *another* layer that also needs to be tested though.
@fuzzyman
ReplyDeleteYeah, I'm pretty familiar with mocking too much (both in the context of testing and regular life) ;)
I'm a Java guy so I'm using Spring for all of my injection needs (which is pretty well tested).
Layers are great so long as they add flexibility and don't get in your way.
This should become the holy grail for testing. +42 for dependency injection.
ReplyDeleteLanguages with powerful meta programming -often dynamic languages but not necessarily- surely ease the pain but some caution is needed:
ReplyDeletehttp://fragmental.tw/2008/01/08/please-do-not-break-into/
--
About Depency Injection, it is not about "asking for your dependencies instead of creating them". That's is the Registry pattern:
http://martinfowler.com/eaaCatalog/registry.html
Dependency Injection *is about having all dependencies automagically fulfilled by something* -often a container.
cheers
I disagree, what you are suggesting that "ask for what you need" is not DI but rather Registry doesn't seem right. The registry patten seems to be basically a global object which allows you to retrieve what you want. DI is the concept of an object saying I need all these dependencies to work correctly, and whoever creates the object provides them.
ReplyDeleteThe registry pattern is a global object where you put in whatever you might ever need, and then look them up. THe total antithesis to DI, because it not only makes everything stored in some static context, it also makes life miserable for testing because to test, you then have to setup the registry with your mock instead of the original and let it get it. Nastiness....
Hi,
ReplyDeleteThe Registry object doesn't have to be one global object, actually you could have different registries for different dependencies. You also don't need a static context. I think you may be thinking implementations like JNDI, that are not good at all, but these are problems with the implementation and not the pattern. A problem in the Registry pattern is that someone will have to instantiate the Registry and add dependencies to it.
Anyway, yes, the Registry is the antithesis of Dependency Injection and that's what I've tried to explain in my last comment.
When you say that an object asks for its dependencies you imply that it asks *something* for those. In Dependency injection you won't ask anyone, you just declare your dependencies to whoever is interested in those (via constructor signature or metadata) and assume that those will be fulfilled when you need them.
If you ask something you will have to ask what has your dependencies. The role of Registry is exactly to hold dependencies so clients can ask or them.
So, my conclusion is that if you ask for your dependencies you are often using Registry (as you have to ask somebody). If you declare your dependencies and assume they'll be there when you need you use Dependency Injection.
cheers
ah I see what you are getting at. Well, with the ask approach, I believe what he basically means is that the constructor just says that I need this, and it is up to who ever is using that class to provide it. You can go one step higher and plug in a framework which provides all of these, or you might have to, in your main class, construct the object graph creating all the base and calling these constructors which ask for these things.
ReplyDeleteSo in that way, it moves away from a registry pattern. True you can use a registry pattern to satisfy this as well, but it allows you to inject mocks and ignore objects which you don't care about.
Of course, you could just use Guice or Spring which allows easy dependency injection and managing dependencies which create your object graph, and why wouldn't you ? It makes life so much easier :D
Hm.. I don't know, I think our problem -or my disagreement- is in the semantic of "ask". I don't have any problems with the examples presented here, they *do* represent Dependency Injection for me.
ReplyDeleteThe only point I disagree in this article is this sentence:
"Asking for your dependencies instead of constructing them withing the application logic is called 'Dependency Injection' and is nothing new in the unit-testing world.".
As I said DI (and the example provided in this post) is not about asking anything, it is about declaring your dependencies.
The House featured in this text won't ask for a Kitchen, it just declares that it needs one. Asking for a kitchen would be something like:
class House {
private final Kitchen kitchen;
public House(Kitchen kitchen) {
this.kitchen = somethingThatHasKitchens.getKitchen();
}
//...
}
And that's not what happens. With or without a proper framework like guice or Spring or Pico the House in the example just declares what it needs, it won't ask for a dependency or create their own objects.
cheers
For people who think that this article does not apply to dynamic languages (as some of you pointed out in the comments) I would like to point to: http://www.testabilityexplorer.org/blog/2008/07/13/dynamic-languages-are-not-more-testable
ReplyDelete@Misko
ReplyDeleteMost of the points you make in your "dynamic languages are not more testable" seem to be based on a misunderstanding of the testing techniques used in dynamic languages.
Patching global state is dangerous - but can easily be localised to within the body of a single test method (e.g. by a decorator in Python). You are right that it makes parallelising tests harder - personally I prefer to parallelise tests across several machines though (or use multiple isolated interpreters to parallelise which also avoids the 'global' problem).
More to the point, mutating global state (class level attributes) is only one small technique - more importantly you can patch *instance* methods without touching the class at all, and can trivially use mocks without having to jump through hoops (duck typing rather than static typing).
Dynamic languages are *significantly* easier to test - for many reasons.
I really don't get this. Why can't I just have a 'new' command in the House's constructor? That's what the constructor is for in my understanding of object oriented design.
ReplyDeleteIn my test environment I then just swap out the Kitchen and put in a Mock Kitchen:
House myHouse = new House();
myHouse.kitchen = new Mock();
.. I may be completely wrong, I'm a noob to unit testing.
The benefit of dependency injection becomes obvious if the Kitchen is really expensive to instantiate, and especially if it is not strictly needed for all your tests of House. In fact, it should not be needed at all and should be tested in a separate test fixture. The collaboration between classes are integration tests and should not be part of unit tests.
DeleteAnother scenario would be if you have a dependency that communicates with a slow data source (e.g. a remote database). You would rather inject a mocked database provider. This would also benefit from using interfaces to separate interface from implementation, which is not covered in this article.
Loved the article. Doesn't say anything about how you should test the Factory class though...
ReplyDelete