Web Apps in TDD, Part 2

Second step

The goal is still the same : showing a simple city map with the main character in the middle. I now have made sure I can answer http calls, thus I’ll pass to declare something about the contents of the answer itself. The simplest version of a city is a building (disclaimer : Christopher Alexander might not agree).

So, what’s a building? A rectangle, we said. But not just any rectangle, it should be shown on a browser, so it’s a rectangle within an html document.

A successful HttpAnswer must thus contain an html document.


@Test
public void shouldAnswerWithAnHtmlDocument() throws Exception {
    new Boss(PORT);
    assertThat(Http.callOn(PORT), is(HttpAnswer.with("<html><body></body></html>")));
}

When I run this test though, I get that PORT is already used. This can mean just one thing, my Boss server from the previous test is still running. I must stop it after the test.
Thus I change the previous test as well, to add a stop operation to Boss.


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

Then I implement it in Boss. This should do it :


public class Boss {
    private final SelectorThread selector;

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

    public void stop() {
        selector.stopEndpoint();
    }
 }
 

Notice how the Grizzly selector now must be a field of Boss.
The test is still red, but now it’s not due to the need for the port, but because of this :


 Expected: is <org.boss.HttpAnswer@7f60c4b0[statusCode=200,payload=<html><body> </body></html>]>
     got: <org.boss.HttpAnswer@2a114025[statusCode=200,payload=<null>]>

Which is exactly what I need. I’ll make this pass then refactor some more.
To make this test pass the obvious thing to do is to let Boss add the html page to each answer.


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

I should not forget to let my little Http utility class parse the payload back (well, if I forgot my test would stay red, so I would know).


public class Http {
...
    public HttpAnswer call() throws IOException {
        Page page = client.getPage("http://localhost:" + port);
        int statusCode = page.getWebResponse().getStatusCode();
        String payload = page.getWebResponse().getContentAsString("UTF-8");
        return new HttpAnswer(statusCode, payload);
    }
...
}

The test is now green, I’m correctly sending back some basic html component.
Yet, there’s a surprise, the first test is not passing anymore. The previous test was expecting an empty http answer with just the success code 200.

Now, I might of course erase the old test, reasoning that it was just a stepping stone to the current test, which is, itself, just there to let me get closer to the goal of my first feature: showing the city map with the character in the middle.

Microdivergence

Yet I won’t. I’ll try to have both tests succeed.

If Boss is the game, then my html declaration is the screen on which the game will appear, and the first test is simply not expecting a screen.


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

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

    @Test
    public void shouldAnswerWithAnHtmlDocument() throws Exception {
        Boss boss = new Boss(PORT, new HtmlScreen());
        HttpAnswer answer = Http.callOn(PORT);
        boss.stop();
        assertThat(answer, is(HttpAnswer.with("<html><body></body></html>")));
    }
}

Here it is, a new interface, representing the screen on which game objects will appear. The interface is still quite simple :


public interface Screen {
    String render();
}

And of course the HtmlScreen renders the html and body tags, while the BlankScreen renders nothing.

Now that all tests are green I’ll proceed to some more refactoring.

I don’t like the duplication of the boss.stop() operation I see in the test. I also don’t like my test to concern itself with two different different levels of abstraction : the overall behavior (it returns an html document) and its implementation (the html document is <html><body>…</html>)


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

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

    @Test
    public void shouldAnswerWithTheScreenContents() throws Exception {
        Screen screen = new HtmlScreen();
        boss = new Boss(PORT, screen);
        assertThat(Http.callOn(PORT), is(HttpAnswer.with(screen.render())));
    }

    @After
    public void stopBossServer() {
        boss.stop();
    }
}

Presently I added a new test which is one abstraction level lower than my first test, it’s not concerned about the whole system, just the game general presentation :


public class HtmlScreenBehavior {
    
    @Test
    public void shouldRenderAnHtmlDocument() {
        assertThat(new HtmlScreen().render(), is("<html><body></body></html>"));
    }
 
}

Notice how the concept of Screen was born to allow the two tests (“just return the 200 OK” and “return an html document”) to work at the same time.

The force of two similar-yet-diverging tests makes subsystems spring out. Waiting to have two divergent stories is often too late, while very small steps in a single story generate divergence at will.

The movement downwards

Finally, the third test and the Screen interface herald the birth of a gui layer. This is a movement which is very important.

Just as having a first test stating the basic broad facts of a system makes it flexible, using that same level of abstraction to declare all and everything in the system makes the tests rigid and instantly kills the design feedback from tdd.

When the next bit of the story can be declared by using a new, smaller part of the system, we need to focus on declaring it with just that part. If I manage to do it then it means that part has a reason to exist.

There’s something profoundly wrong in a system where the only useful object is the system itself. This is why acceptance tests have little value as a design tool.

There’s also something wrong in skipping the first tests of a new system just because that system happens to be a web application. It generates plenty of urban legends on how tdd works only for the business domain.

By the way, what’s this infamous business domain?

Anyway, let me move on with the next part : showing a building on my screen!

Reality check

My very first tests enforce lots of stuff, but I would really like to see how this system works for real. In case I forgot something.
The quickest way to do it is to make it run. I do this by this very complex method in the Boss class :


public class Boss() {
    public static void main(String... args) throws Exception {
        new Boss(11111, new HtmlScreen());
    }
    ...
}

Then I fire up my browser and type http://localhost:11111/
Surprise! the browser is not parsing the html!

In fact a browser will not parse the html unless the content is declared as being html.
Time to update my tests. I’ll just include the content type in my definition of correct HttpAnswer:


    public static HttpAnswer ok() {
        return new HttpAnswer(200,"","text/html");
    }

Now, when I perform an Http call I’ll also read the content type and use it to initialize the answer. Result, a red test, the very first one :


Expected: is ...HttpAnswer...contentType=text/html]
     got: ...HttpAnswer...contentType=text/plain]

Quick to fix, the HttpAnswer should set the contentType.


public class HttpAnswer{
...
    public void writeTo(Response response) throws IOException {
        response.setStatus(statusCode);
        response.setContentType(contentType);
        ByteChunk chunk = new ByteChunk(payload.length());
        chunk.setBytes(payload.getBytes("UTF-8"),0,payload.length());
        response.doWrite(chunk);
    }
...
}

Test green.
Just a little refactoring to clean this code a bit in order to maintain the same level of abstraction throughout the writeTo method :


    public void writeTo(Response response) throws IOException {
        response.setStatus(statusCode);
        response.setContentType(contentType);
        response.doWrite(payloadAsByteChunk());
    }

8 thoughts on “Web Apps in TDD, Part 2

    1. Hello,
      first of all, thanks for focusing back my attention to this post. I noticed that in the last update the formatting for the html bits was simply wrong for wordpress and that had erased the html and body tags from the code samples. Maybe if you re-read it now it will make more sense.

      About the test double : test double is pretty generic, I could point out that BlankScreen is pretty much a test double (specifically, a stub). Yet this doesn’t allow for just one test. May I ask you to elaborate further? Are you thinking of a mock?

      1. Yes, with a mock you can do only one test: delegates rendering to Screen. You can check the object returned by render() is exactly the same returned in the right field of the http response.

  1. Hello again.
    What’s the advantage of declaring something about Boss’ internal structure : “Boss delegates rendering to Screen”?
    In the second test, anyhow, I’m already testing that httpResponse contains exactly what HtmlScreen.render returns.
    I don’t see how declaring the interaction would remove a test and still declare the interesting behaviour?
    I see how one could compact the first and second test into one, at the price of one, more complex test (with or without mocks), but I’m not sure this is what you mean.

    An example of what you mean would be a very useful base for further discussion.

    1. Don’t know which mocking framework you use, so with a private class a unique test will be something like:
      @Test
      public void shouldAnswerWithTheScreenContents() throws Exception {
      Screen screen = new MockScreen();
      boss = new Boss(PORT, screen);
      assertThat(Http.callOn(PORT), is(HttpAnswer.with(“some content”)));
      }
      private MockScreen implements Screen {
      public String render() { return “some content”; }
      }

      The reason I pushed for a Test Double is that I read these tests as unit ones, since you’re building the object graph by hand (probably you intended them as end-to-end instead).
      With a Test Double, regressions in Screen implementations (like throwing an exception in render()) make an hypothetical HtmlScreenTest fail, but not BossBehavior tests. With testing interactions at the unit level, defect localization becomes easier and you have less cases to cover: of course in this case the interaction is declared as a Mock expectation.

  2. Hello again.
    In the years I used a few : JMock, Easymock, JMockit and now Mockito.

    The solution you propose is very similar to my BlankScreen, a stub, indeed.

    You didn’t offer a direct example of the defining use of mocks (expectations), but I think I see what you would write.

    I tell you why I stopped doing it the way you suggest :

    1 – If the goal is declaring behaviors, thus designing the what, I rather use objects that flesh out the metaphor instead of technically named classes, thus BlankScreen instead of ScreenStub.

    2 – If the goal is, once behaviors are implemented, designing the how through refactoring, I prefer having tests at level n depend on n+1 because that reminds me that real objects should stay really isolated : if all I need to ensure me that n does not depend on n+1 is a mock then what is to force “render()” to stay simple and backslash-free?

    3 – If there’s value in separating these two goals in different moments, then the last thing I want is having to think about both in one place, the test. And writing expectations is thinking about the implementation.

    4 – I stopped removing the “simpler” tests when I noticed that it was a shortcut to avoid confrontation with divergence (see the main text).

    5 – If I target progressively focused tests : from high-level tests which only test very simple cases, down to low-level tests which describe complex and corner cases I get the most design feedback and just enough isolation to keep defect localization quick.

    6 – The key to having a low number of cases is not to stay always high up, but to move downwards and separating the responsibilities to let you really step down, the technology you use to take that step down is inconsequential to that respect, thus I use the least amount of technology I can.

    This, of course, may change in the future. Since I presume you can read Italian, if you are interested, here’s my point of view on multiple techniques from a few years ago (2006) : https://minnenratta.wordpress.com/2006/10/23/stairway-to-hell/

    Don’t miss the enthusiastic entry named “Mocking e stubbing selvaggio” :)

    1. Specifically, I think BlankScreen is a Null Object more than a Test Double. What I learned from this exchange is we can use a couple of Null Object and realistic object to drive the interface of a collaborator, instead of just introducing the real one. It should be somewhat similar to the Triangulation technique described by Kent Beck in TDD by Example.

      1. Yes, its role is to represent an empty screen, thus it is an implementation of a null object.
        Mind you though, here I’m not driving the interface of the collaborator through the test, that I do during refactoring, this separation I found to be very important.

        Indeed it is a kind of triangulation, one which might not be required for evolving the algorithm, but that I think is healthy for design.

        Thanks a lot for the comments!

Leave a reply to Giorgio Sironi Cancel reply