Analysis in philosophy or logic is essentially cutting a conceptual whole to smaller pieces. This is easily connected to agile software development with its hierarchy of a software being analysed to releases, releases analysed to user stories, then to developer stories, and finally to developer tests. (Peter Schrier crystalised this to me in his March 2005 Agile Finland presentation (PDF).)

Software analysed

Robert C. Martin has written a post
“Analysis Vs. Design” where he makes the point that analysis and design in making software are just two different points of view on the same issue. So my word-play of “analytical design” really means exploring this idea in the context of programming (which I believe to be creating the software design). The first developer tests prompted by the tasks at hand can serve as top-level starting points for the analytical design of the actual software component being programmed.

There was also discussion on the TDD Yahoo group in November 2005 on what I find a symptom of this top-down design brought up by TDD. When you start from the top, you easily “bite off more than what you can chew.” When this happens, you must temporarily switch your focus to the smaller detail and test-drive that detail before returning to view the bigger picture. For example, if your task at hand needs a non-trivial String.replaceAll() call involving regular expressions containing metacharacters, you are better off pausing for a while and writing a test that just checks that your replaceAll() call does what you want. This is especially important when you are writing a slow integration test instead of a fast unit test, because if you can test the detail in a fast unit test, you’ll get feedback sooner, and better code coverage by unit tests.

The theme comes up in varying forms, such as the problem of “Mother, May I” tests of Tim Ottinger or Mika Viljanen figuring out what tests to write. In these situations, it clearly helps to make the bootstrap tests as close to the business requirements as possible, and then analyse towards the details. Sven Gorts has written a nice discussion explicitly comparing top-down and bottom-up TDD, and reading it reinforced my opinion that top-down TDD is the way to go.

So to make an example of this, I’m pretending to start to work on a Scrum tool. Let’s imagine that the most critical feature is to see the sprint burndown chart, so I’ll start the implementation with this simple test:

package scrumtool;import junit.framework.TestCase;

public class SprintBurndownTest extends TestCase {
    public void testRemainingIsSumOfRemainingOfTasks() {
        SprintBurndown chart = new SprintBurndown();
        Task t = new Task("Paint the burndown chart", 4);
        chart.add(t);
        assertEquals(4, chart.remaining());
    }
}

This prompts me to create new classes SprintBurnDown and Task, so I’ll do just that. With the quick fix / intention features of the IDE, it’s easy enough to fill in the Task constructor and the add as well as the remaining method of SprintBurndown.

I have a habit (that I believe I got from Jukka) of configuring my IDEs so that every generated method implementation just throws a new UnsupportedOperationException. So the IDE code completion only gets the test to compile, but test execution crashes on the second line on the exception thrown by the Task constructor. For now, I’ll just empty the methods, and put remaining() to return -1 because it needs to return something.

This gets me to this test failure that I wanted:

junit.framework.AssertionFailedError: expected: <4> but was: <-1>

So I make the simplest possible change to make the test pass:

package scrumtool;public class SprintBurndown {

    public void add(Task t) {
    }

    public int remaining() {
        return 4;
    }
}

And ta-da, we’re on green.

Notice that the implementation doesn’t do anything with the Task class. Task was only created because the best bootstrap test case that I came up with needed it. And it should be even more obvious that the current implementation of remaining() will fail miserably in more complex usage scenarios ;), which hints me that I might be correct in wanting a Task concept to help in dealing with that complexity. (Or, I might be mistaken, and I should have started without the Task class, for example just passing Strings and ints to SprintBurnDown.add(). Sorry if this bothers you, but this is the best and most real-world-resembling example that I could come up with.)

Despite good examples, I have not yet learned to thrive for having only one assertion per test, nor to use separate JUnit fixtures efficiently. Rather I want my tests to be good examples of what the code should do. So I will go on making my test method tell more of how the software under test should behave.

public void testRemainingIsSumOfRemainingOfTasks() {
    SprintBurndown chart = new SprintBurndown();
    Task t = new Task("Paint the burndown chart", 4);
    chart.add(t);
    assertEquals(4, chart.remaining());

    Task t2 = new Task("Task can be added to a sprint", 2);
    chart.add(t2);
    assertEquals(4 + 2, chart.remaining());
}

Happily this gives me just the failure I wanted:

junit.framework.AssertionFailedError: expected: <6> but was: <4>

And now to get the test pass, I really feel like I need to make use of Task. I want to add behaviour to Task test-driven; the problem of the burndown chart has been further analysed and we have encountered the Task class.

At this point, it might be a good idea to temporarily comment out the currently failing assertion, as in orthodox TDD there must be only one test failing at a time, and I am just about to write a new failing test for Task.

This is the new test for Task and the implementation that got it to succeed:

// TaskTest.java

package scrumtool;import junit.framework.TestCase;

public class TaskTest extends TestCase {

public void testRemainingIsInitiallyOriginalEstimate() {
        Task t = new Task("Tasks can be filtered by priority", 123);
        assertEquals(123, t.getRemaining());
    }
}

// Task.java

package scrumtool;

public class Task {
private int remaining;

public Task(String name, int estimation) {
        this.remaining = estimation;
    }

public int getRemaining() {
        return remaining;
    }
}

And after this, it was easy enough to make SprintBurndown so that the whole test passes:

package scrumtool;public class SprintBurndown {
private int remaining = 0;

public void add(Task t) {
        remaining  += t.getRemaining();
    }

public int remaining() {
        return remaining;
    }
}

Now the whole test passes! So I’ll clean up the test class a bit.

package scrumtool;import junit.framework.TestCase;

public class SprintBurndownTest extends TestCase {
    private SprintBurndown chart = new SprintBurndown();

    public void testRemainingIsSumOfRemainingOfTasks() {
        addTask("Paint the burndown chart", 4);
        assertEquals(4, chart.remaining());
        addTask("Task can be added to a sprint", 2);
        assertEquals(4 + 2, chart.remaining());
    }

    private void addTask(String name, int estimate) {
        Task t = new Task(name, estimate);
        chart.add(t);
    }
}

In case the point was lost in the midst of the many lines of code produced by a rather simplistic example, here it is again:

  1. Write your first programmer tests as high-level acceptance tests,
  2. and when making them pass, don’t hesitate to step to lower levels of analysis when encountering new non-trivial concepts or functionality that warrant their own tests.
Advertisement