Sunday, October 19, 2008

My main() Method Is Better Than Yours

By Miško Hevery

People are good at turning concrete examples into generalization. The other way around, it does not work so well. So when I write about general concepts it is hard for people to know how to translate the general concept into concrete code. To remedy this I will try to show few examples of how to build a web application from ground up. But I can't fit all of that into a single blog post ... So lets get started at the beginning...

Here is what your main method should look like (no matter how complex your application) if you are using GUICE: (src)

public static void main(String[] args)
throws Exception {
// Creation Phase
Injector injector = Guice.createInjector(
new CalculatorServerModule(args));
Server server = injector.getInstance(Server.class);
// Run Phase
server.start();
}

Or if you want to do manual dependency injection: (src)
public static void main(String[] args)
throws Exception {
// Creation Phase
Server server = new ServerFactory(args)
.createServer();
// Run Phase
server.start();
}

The truth is I don't know how to test the main method. The main method is static and as a result there are no places where we can inject test-doubles. (I know we can fight static with static, but we already said that global state is bad here, here and here). The reason we can't test this is that the moment you execute the main method the whole application runs, and that is not what we want and there is nothing we can do to prevent that.

But the method is so short that I don't bother testing it since it has some really cool properties:

  1. Notice how the creation-phase contains the code which builds the object graph of the application. The last line runs the application. The separation is very important. We can test the ServerFactory in isolation. Passing it different arguments and than asserting that the correct object graph got built. But, in order to do that the Factory class should do nothing but object graph construction. The object constructors better do nothing but field assignments. No reading of files, starting of threads, or any other work which would cause problems in unit-test. All we do is simply instantiate some graph of objects. The graph construction is controlled by the command line arguments which we passed into the constructor. So we can test creation-phase in isolation with unit-test. (Same applies for GUICE example)

  2. The last line gets the application running. Here is where you can do all of your fun threads, file IO etc code. However, because the application is build from lots of objects collaborating together it is easy to test each object in isolation. In test I just instantiate the Server and pass in some test doubles in the constructor to mock out the not so interesting/hard to test code.


As you can see we have a clear separation of the object graph construction responsibility from the application logic code. If you were to examine the code in more detail you would find that all of the new operators have migrated from the run-phase to creation-phase (See How to Think About the "new" Operator) And that is very important. New operator in application code is enemy of testing, but new in tests and factories is your friend. (The reason is that in tests we want to use test-doubles which are usually a subclass or an implementation of the parent class. If application code calls new than you can never replace that new with a subclass or different implementation.) The key is that the object creation responsibility and the the application code are two different responsibilities and they should not be mixed. Especially in the main method!

A good way to think about this is that you want to design your application such that you can control the application behavior by controlling the way you wire the objects together (Object collaborator graph). Whether you wire in a InMemory, File or Database repository, PopServer or IMAPServer, LDAP or file based authentication. All these different behaviors should manifest themselves as different object graphs. The knowledge of how to wire the objects together should be stored in your factory class. If you want to prevent something from running in a test, you don't place an if statement in front of it. Instead you wire up a different graph of objects. You wire NullAthenticator in place of LDAPAuthenticator. Wiring your objects differently is how the tests determines what gets run and what gets mocked out. This is why it is important for the tests to have control of the new operators (or putting it differently the application code does not have the new operators). This is why we don't know how to test the main method. Main method is static and hence procedural. I don't know how to test procedural code since there is nothing to wire differently. I can't wire the call graph different in procedural world to prevent things from executing, the call graph is determined at compile time.

In my experience that main method usually is some of the scariest code I have seen. Full of singleton initialization and threads. Completely untestable. What you want is that each object simply declares its dependencies in its constructor. (Here is the list of things I need to know about) Then when you start to write the Factory it will practically write itself. You simply try to new the object you need to return, which declares its dependencies, you in turn try to new those dependencies, etc... If there are some singletons you just have to make sure that you call the new operator only once. But more on factories in our next blog post...

Link - from Google Testing Blog
Related:
Root Cause of Singletons
TotT: Sleeping != Synchronization
Where Have All the Singletons Gone?
Singletons are Pathological Liars

No comments: