Web Apps in TDD, Part 1

What’s this?

This is the beginning of the paper I was writing when discussing TDD with Matteo before he kindly invited me to the Italian Agile Day 2010.
After the talk, and in the days since, I was asked many times to provide more details and the source code.
The first request is certainly best served by publishing the aforementioned paper : it is not complete, so far it is 20 pages, which were enough for my talk, but I think it will take roughly twice that to describe TDD, the way I do it for web applications.
Nonetheless I’ll start posting the paper as it is right now, and more, as I continue the development and the paper itself.

I’ll abide to the second request once the full paper is complete by releasing the full sources along with the pdf version.

So, without further ado, here’s part one : page 1 to page 4.

The context

Here I mean to explain how I use test driven development to help me craft web applications. This is my way of doing it, as it stands now, having evolved during the years through the various systems I had the pleasure to create.

Since I’ve long wished to create a gta-like web game I’ll use it as an example for a new web application.
The game name is “Boss” and it should be played over a browser with a point-and-click interface.
When Boss is called by http it should offer a bird view of a set of city blocks, with the main character standing in the middle of the map. Each city block should be composed of multiple rectangles representing the various buildings.
The main character, a colored dot, should move in the direction clicked by the user’s mouse on the map.

An unusual first step

I’ll start with the first feature : when Boss is called by http it should offer a bird view of a set of city blocks, with the main character standing in the middle of the map.

Well, that’s certainly a lot of stuff to implement and I strongly doubt I’ll be able to implement it within a few minutes, so I’ll try to trim down the problem to be able to write the first test and its solution quickly.

With my first test I’ll just make sure that Boss answers to an http call succesfully. For the sake of brevity I’ll skip a couple loops and presume that a class “Boss” should exist, instead of refactoring it out of the test itself. So, my first test looks like this :


public class BossBehavior {

    @Test
    public void shouldAnswerHttpCall() throws IOException {
        new Boss(8080);
        WebClient client = new WebClient();
        Page page = client.getPage("http://localhost:8080");
        assertThat(page.getWebResponse().getStatusCode(), is(200));
    }

}

WebClient comes with the htmlunit framework, which turns out to be a quick way to invoke an http endpoint.

Of course this fails, as expected.

So I’ll try to make it pass. I don’t plan to re-implement the http protocol, so I’ll go shopping for a nice http server. In the past I used Jetty, but this time I’ll try Grizzly.

After a brief tour on the web I try this :


public class Boss {
    
    public Boss(int port) throws IOException, InstantiationException {
        SelectorThread selector = new SelectorThread();
        selector.setPort(port);
        selector.listen();
    }

}

This returns a nice null pointer exception. To be specific I see this line :


java.lang.NullPointerException
	at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:824)

Hm, well, I didn’t pass an Adapter to the SelectorThread. Some more reading and I get this :


public class Boss {
    
    public Boss(int port) throws IOException, InstantiationException {
        SelectorThread selector = new SelectorThread();
        selector.setPort(port);
        selector.setAdapter(new AlwaysReturn200Ok());
        selector.listen();
    }

    private static class AlwaysReturn200Ok implements Adapter {
        public void service(Request request, Response response) throws Exception {
            response.setStatus(200);
        }

        public void afterService(Request request, Response response) throws Exception {}
    }
}

It works fine, the test is green.
Now I’ll proceed to some refactoring, but first, a brief note : is this a unit test?

Actually, I don’t care. The only thing which I care about is that this test is quick, repeatable, short, simple and it will work everywhere I have port 8080 available.

In fact, port 8080 is quite used nowadays (default tomcat servers for development purposes and so on…), let’s change that to 11111 from now on. Just to stay safe.

And now some refactoring

First the test, when I switched to port 11111 I forgot to update the url on which WebClient was performing the call, a nice reminder of that clumsy duplication I introduced.
Also, the test is a bit too involved in the details of WebClient, let me fix this before moving to refactoring Boss itself.

I stop when I get to this :


public class BossBehavior {
    private static final int OK = 200;
    private static final int PORT = 11111;

    @Test
    public void shouldAnswerHttpCall() throws Exception {
        new Boss(PORT);
        assertThat(Http.callOn(PORT), is(OK));
    }
}

Http contains little special, just the call to WebClient and the extraction of the status code.

Now I’ll move to Boss. Currently Boss is just a tiny http server which always answers 200 OK. From a responsibility point of view there’s little to do, but I definitely don’t like that “200” which appears both in the test and the solution.

In the context of my application the code 200 is just a way to say the answer is positive. I don’t like using primitives to represent high-level concepts, such as “positive answer” and I certainly don’t like duplicated primitive values.


public class HttpAnswer {

    private final int statusCode;
       
    public static HttpAnswer ok() {
        return new HttpAnswer(200);
    }

    public HttpAnswer(int statusCode) {
        this.statusCode = statusCode;
    }

    public void writeTo(Response response) {
        response.setStatus(statusCode);
    }
...
}

While a bit verbose, this class removes the status 200 duplication in test and solution code and it slims down both, while letting me express the intent more clearly. Here’s the current situation for both test and solution :


public class BossBehavior {
    
    private static final int PORT = 11111;

    @Test
    public void shouldAnswerHttpCall() throws Exception {
        new Boss(PORT);
        assertThat(Http.callOn(PORT), is(HttpAnswer.ok()));
    }
}

public class Boss {
    
    public Boss(int port) throws IOException, InstantiationException {
        SelectorThread selector = new SelectorThread();
        selector.setPort(port);
        selector.setAdapter(new AlwaysReturn(HttpAnswer.ok()));
        selector.listen();
    }

}

To make the tests assertions work and to get nice messages in the future I’ll implement equals, hashcode and toString for HttpAnswer. I use the excellent EqualsBuilder, HashcodeBuilder and ToStringBuilder classes from apache.


public class HttpAnswer {
...
    @Override
    public boolean equals(Object other) {
        return EqualsBuilder.reflectionEquals(this, other);
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}
Advertisements

7 thoughts on “Web Apps in TDD, Part 1

  1. Sure, as mentioned above, once the paper is complete I’ll release the code along with it. Until now I coded without versioning (up to the buildings and character step, the last one of the talk), which is a pity, when I commit the final version of the code I’ll try to provide the full history. For instance I’ll start committing to a git at each step from now on.

  2. Hello Franco,
    it is coupled indeed, but I don’t think it’s too much coupled (in the current state, in the first phases as in this post it’s totally Grizzly-related because it’s all about http).
    Currently I don’t care about technological coupling as long as this coupling is limited to the code which manages that technology (in this case it’s http). I’m fine as long as I don’t have logic which has nothing to do about http that happens to know about Grizzly.
    This is because I see no problem in writing a new http layer when I change the http implementation, I see a problem if I have to touch something else.

    I have one itch so far : the url mapping is a private method inside a Grizzly adapter. Url mapping should not be related to an http technology, but I tolerate it because so far it’s just two lines.

    It has not always been so. In the past I would have isolated the http implementation further (for instance I would have wrapped Request and Response at once. Now I do it as soon as I catch a responsibility to give those wrappers.
    The reason I changed is that I found wrappers with no responsibility except isolation not to be worth the extra complexity.

    It’s an open and interesting point so I would like to ask you where you see trouble with the current coupling.

  3. Carlo,
    you explained your choices so well, and I think you’re right. I had some doubts about adding an extra layer of encapsulation of the web engine, but your answer about the trade-off between encapsulation and simplicity is probably correct.
    At the beginning, what I found strange in your approach is that the very first choice you made in this example was about the web server you had to use. I understood that this is an example focused on technology, specifically created to show that even technology aspects can be designed using TDD, Anyway, it sounded so different from the usual way I design web apps, where the web server is not considered as a part of the problem.
    This is such a big shift in the way of thinking, that I need some time to fully metabolize it!

    Thank you for sharing your experience with us!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s