Small and Simple Web Applications - the Friki Way (Part 2)

Frank Carver, March 2003

Abstract

This article is the second of a series which laments the bloated and unmaintainable state of so many J2EE web applications and looks at ways to keep web applications small, simple and flexible. The series uses the author's Friki software as a case study, and discusses ways to design and build a powerful, flexible and usable web application in less space than a typical gif image.

This article continues the design process begun last time, and starts the serious work of coding a solution. On the way it discusses some techniques to help make software development more trustworthy and less stressful.

Introduction

If you've read the first article, you should be aware that the aim of this project is to develop a small, simple and understandable "Wiki" (editable web site) application. We've considered and decided to defer the relatively heavy decision of how to store and retrieve pages by introducing a Java interface, and thought through some ways of generating the HTML for each displayed page.

Before we really get started, there's one vital question which needs to be answered. It's such an important question, that it's often forgotten until it's too late!

I recommend that this question be asked and answered at the start of every software development project. I'm not suggesting that anyone build complex and detailed plans (also known as "guesses" or "wishes") before starting work. I'm not suggesting that some all-powerful "architect" lay down a complete design before starting the real work. I'm not suggesting that everything should be described in UML or pseudo code. Almost the opposite.

How will we know when we're done? is a simple plea to whoever wants the work done. A development team needs to know when to stop developing. Everyone involved in design and coding a solution needs to have an idea of the end goal, if they are to make sensible decisions along the way. The answers to this question can vary enormously, but beware of "answers" which don't actually answer the question: "stop when I tell you to stop" might as well be "don't bother doing any work, it won't make any difference"; "stop when the customer is happy" gives no guidance on what makes the customer happy - why not just buy him or her a beer and go home?

Useful answers to this question include things like "when it does this and this and this", "when average response time is less than 5 seconds under peak load" and so on. What these answers have in common is that they are measurable and testable. You could (theoretically, at least) make a test, and when it passes you can stop developing.

So let's ask our "virtual customer" this hard but very useful question. How will we know when we are done ?

For the purposes of this article, our customer says we will be "done" when we have a Java web application in which:

  1. each page may be viewed using it's own unique URL
  2. page content may contain links to other pages by name
  3. links to nonexistent pages will be marked with a "?"
  4. page content may be created and edited using just a browser
  5. at least 100 different pages can be stored
That should be enough to get started. Let's see how simply and quickly we can code a solution to this.

Just one final reminder. The above points are our complete "acceptance criteria". Any solution which meets these goals is a valid one. We must free our minds from imagining any "requirements" which have not been asked for.

What do we write first?

So we know what we have to do, but we don't know where or how to start. Strangely enough, I'm not going to start with design. I'm not even going to start with coding a solution. I'm going to start with a test! I always start with a test to make sure I can compile and run something. Without that, there's not much point putting in a lot of effort to write any code. In this case, I'll use the JUnit test framework, which I've found very handy over the years. If you don't already have a recent version of JUnit, please download it from the above link before proceeding.

AllTests.java

package tests;

import junit.framework.*;

public class AllTests extends TestCase
{
    public static TestSuite suite()
    {
        TestSuite ret = new TestSuite();

        ret.addTest(new TestSuite(EmptyTest.class));

        return ret;
    }
}

If we try and compile this, it should fail. It should either complain about the absence of junit.framework in the import statement (which hints that the file "junit.jar" from the JUnit distribution needs to be in the classpath) or complain about the absence of the tests.EmptyTest class (which is fine. We haven't written it yet!). If it compiles without error you have either tried to compile the wrong file, or you already have both JUnit and a class tests.EmptyTest in your classpath, and you'll need to sort that out before we progress any further.

The most important thing to take away from this is that a failing test has given us a lot of information and confidence about the state of our system. The EmptyTest class still doesn't exist, so we can proceed and write it.

EmptyTest.java

package tests;

import junit.framework.*;

public class EmptyTest extends TestCase
{
    public void testEmpty()
    {
    }
}

Now try and compile these two classes. They should compile OK. We've passed our first test! Of course our "system" doesn't do much. There is some test code, but no actual product, but we know that we can compile some real Java code, including all that stuff about jar files and classpaths. And we have the start of a test "scaffolding" to help us build the real code.

The next tiny step is to run the test code. Type:

    java junit.swingui.TestRunner tests.AllTests

You should see a nice graphical dialog with a green bar indicating that 1 test has been run, with no tests and no failures. Excellent. We are now ready to write a real test for a real feature.

A first feature

Last session I promised that we'd get started on our template system, so let's begin with that. Just as above, the process is to start with a test. Even with a tiny addition to some existing software, the most important question is still "How will we know when we're done?". Simple is good, so let's start simple - as simple as we can. Let's test that a template with no characters in it "expands" to a template with no characters in it. How about something like:

TemplateTest.java

package tests;

import junit.framework.*;

public class TemplateTest extends TestCase
{
    public void testEmptyTemplate()
    {
        TemplateEngine engine = new TemplateEngine();
        assertEquals("", engine.expand(""));
    }
}

Then add the line

        ret.addTest(new TestSuite(TemplateTest.class));
to Alltests.java, next to the similar line for "EmptyTest"

Trying to compile our system again should now tell us that there is no TemplateEngine. If you look carefully, though, it's actually complaining that there is no class "tests.TemplateEngine". To fix this we can make two changes. Add the line

import friki.TemplateEngine;
to TemplateTest.class and create a new class - the first of our real code:

TemplateEngine.java

package friki;

public class TemplateEngine
{
    public String expand(String input)
    {
        return input;
    }
}

Hold on. Isn't that cheating? That method will never "expand" anything! Pah!

This is very important. Look back to where I said "We must free our minds from imagining any "requirements" which have not been asked for". The code we have written passes all our tests. If you think it should do more, you are guessing.. Worse than that, there is a much more important problem with that code which we'll see in a minute. But first, if we want more code, we have to know when to stop coding. So we need more tests.

So let's think about an actual substitution. Say we want to substitute all occurrences of "~name~" with "Frank":

TemplateTest.java

package tests;

import junit.framework.*;
import friki.TemplateEngine;

public class TemplateTest extends TestCase
{
    public void testEmptyTemplate()
    {
        TemplateEngine engine = new TemplateEngine();
        assertEquals("", engine.expand(""));
    }

    public void testSingleToken()
    {
        TemplateEngine engine = new TemplateEngine();
        assertEquals("Frank", engine.expand("~name~"));
    }
}

We run the tests. They fail. Our TemplateEngine gives back "~name~", which is obviously not the same as "Frank". So let's fix the code

TemplateEngine.java

package friki;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;

public class TemplateEngine
{
    public String expand(String input)
    {
        boolean inToken = false;
        StringBuffer token = new StringBuffer();
        StringBuffer ret = new StringBuffer();

        CharacterIterator it = new StringCharacterIterator(input);
        for(char c = it.first(); c != CharacterIterator.DONE; c = it.next())
        {
            if (c == '~')
            {
                if (inToken)
                {
                    ret.append("Frank");
                    token.setLength(0);
                }

                inToken = !inToken;
            }
            else
            {
                if (inToken)
                {
                    token.append(c);
                }
                else
                {
                    ret.append(c);
                }
            }
        }

        return ret.toString();
    }
}

There are a few things to note about this code:

It's getting better, but still not complete. Yes, we need more tests. So let's see what happens if we ask for something else. Add the following to TemplateTest, and run it again:

    public void testDifferentToken()
    {
        TemplateEngine engine = new TemplateEngine();
        assertEquals("Margaret", engine.expand("~wife~"));
    }

The test fails, of course. Apparently I'm married to myself. Much though I like the name Frank, I think it's unfair to return it as the value of every token. But that "Frank" is hard coded. If we want to return "Frank" for one token, and "Margaret" for another we have to get the names and values from somewhere. Sounds like a Map to me:

TemplateEngine.java

package friki;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Map;
import java.util.HashMap;

public class TemplateEngine
{
    private Map values;

    public TemplateEngine()
    {
        values = new HashMap();
        values.put("name", "Frank");
        values.put("wife", "Margaret");
    }

    public String expand(String input)
    {
        boolean inToken = false;
        StringBuffer token = new StringBuffer();
        StringBuffer ret = new StringBuffer();

        CharacterIterator it = new StringCharacterIterator(input);
        for(char c = it.first(); c != CharacterIterator.DONE; c = it.next())
        {
            if (c == '~')
            {
                if (inToken)
                {
                    ret.append(values.get(token.toString()));
                    token.setLength(0);
                }

                inToken = !inToken;
            }
            else
            {
                if (inToken)
                {
                    token.append(c);
                }
                else
                {
                    ret.append(c);
                }
            }
        }

        return ret.toString();
    }
}

Great. The basic template expansion works, but it still feels a bit clumsy to me. I don't really like the way the values are built in to the class itself. Can we make it simpler and more flexible? I reckon so. let's change our tests a little:

TemplateTest.java

package tests;

import java.util.Map;
import java.util.HashMap;
import junit.framework.*;
import friki.TemplateEngine;

public class TemplateTest extends TestCase
{
    Map values;

    public void setUp()
    {
        values = new HashMap();
        values.put("name", "Frank");
        values.put("wife", "Margaret");
    }

    public void testEmptyTemplate()
    {
        TemplateEngine engine = new TemplateEngine(values);
        assertEquals("", engine.expand(""));
    }

    public void testSingleToken()
    {
        TemplateEngine engine = new TemplateEngine(values);
        assertEquals("Frank", engine.expand("~name~"));
    }

    public void testDifferentToken()
    {
        TemplateEngine engine = new TemplateEngine(values);
        assertEquals("Margaret", engine.expand("~wife~"));
    }
}

That's better. We now have much more control over how the template expander works. We can give it whatever names and values we want to use, and expect it to fill them in to a supplied template. The "setUp" method is run just before each of the test methods, and makes this test class into what is known as a "fixture".

By the way. Have you noticed that there is a lot of duplication in this code? The code to create a new TemplateEngine is exactly the same in all the test cases. In the spirit of keeping things as small and simple as possible, let's move them into the setUp method as well:

TemplateTest.java

package tests;

import java.util.Map;
import java.util.HashMap;
import junit.framework.*;
import friki.TemplateEngine;

public class TemplateTest extends TestCase
{
    Map values;
    TemplateEngine engine;

    public void setUp()
    {
        values = new HashMap();
        values.put("name", "Frank");
        values.put("wife", "Margaret");
        engine = new TemplateEngine(values);
    }

    public void testEmptyTemplate()
    {
        assertEquals("", engine.expand(""));
    }

    public void testSingleToken()
    {
        assertEquals("Frank", engine.expand("~name~"));
    }

    public void testDifferentToken()
    {
        assertEquals("Margaret", engine.expand("~wife~"));
    }
}

Of course, if we try and run this test it will fail (it won't even compile!). So the TemplateExpander code needs to be brought in line with our new design. Notice how at each stage what we need drives the tests, then the tests drive the implementation.

TemplateTest.java

package friki;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Map;

public class TemplateEngine
{
    private Map values;

    public TemplateEngine(Map values)
    {
        this.values = values;
    }

    public String expand(String input)
    {
        boolean inToken = false;
        StringBuffer token = new StringBuffer();
        StringBuffer ret = new StringBuffer();

        CharacterIterator it = new StringCharacterIterator(input);
        for(char c = it.first(); c != CharacterIterator.DONE; c = it.next())
        {
            if (c == '~')
            {
                if (inToken)
                {
                    ret.append(values.get(token.toString()));
                    token.setLength(0);
                }

                inToken = !inToken;
            }
            else
            {
                if (inToken)
                {
                    token.append(c);
                }
                else
                {
                    ret.append(c);
                }
            }
        }

        return ret.toString();
    }
}

And that is the "heart" of the templating system done. To make double sure it's what we wanted at the start, let's test the example template from last session:

    public void testExamplePage()
    {
        values.put("title", "PageOne");
        values.put("content", "This is the first page in our new Wiki");

        assertEquals(
            "  <html><head><title>Friki: PageOne</title><head>\n" +
            "  <body>\n" +
            "  <h2>PageOne</h2>\n" +
            "  This is the first page in our new Wiki\n" +
            "  <hr width='100%'>\n" +
            "  <a href='edit?PageOne'>edit this page</a>\n" +
            "  </body>\n" +
            "  </html>\n",
            engine.expand(
                "  <html><head><title>Friki: ~title~</title><head>\n" +
                "  <body>\n" +
                "  <h2>~title~</h2>\n" +
                "  ~content~\n" +
                "  <hr width='100%'>\n" +
                "  <a href='edit?~title~'>edit this page</a>\n" +
                "  </body>\n" +
                "  </html>\n"));
    }

5 tests passed. Cool.

Although the implementation given here works as much we need it to (as shown by that last test), you may want to think about what it can't do. What happens if we want to include a '~' character in our template? What happens if we ask it to expand a token it doesn't have a value for? If you are worried about these sort of questions, ask them in the form of a test, then "fix" the code. But remember - every time you add a feature that's not needed by the application right now, you are making the final program bigger, you are making bugs harder to find, you are making the code harder to read. So think. Do you really need that feature yet?

How are We Doing?

We still haven't made a Wiki yet! We have written, compiled, and run some real code to make sure we have a workable build environment.We have a useful "template expander" in a few lines of code which we can use for this project, but might also be handy in others. We have built a complete regression test suite which automatically tests every class and method in our system so if anything breaks we'll know straight away. We can go home confident that we are making real, measurable, repeatable progress, and come back fresh and ready next time.

Next session we'll add more customer features to our Wiki "engine" and look into automating the process of compiling and testing the code even more. In the meanwhile, I recommend reading more about JUnit. If you want to get ahead of the game, you could also look at the HTTPUnit web testing toolkit and the Ant build tool. If you want to read more about keeping the enjoyment in software development, check out my golden rules of stress-free programming.