Key takeaways from my TDD training sessions

Recently I had the chance to participate to a Test Driven Development session. Until now I never tried TDD I just simply used the TFP (Test First Programming), so let’s see what are the differences between Waterfall model, TDD and TFP. After that I’ll list some key points about doing TDD.

Waterfall processProgrammers’ headache started in 1970 when Winston Royce published the Waterfall model. This model was based on a set of sequential steps: Analysis, Design, Code, Test, Maintain. The big assumption made was that the design precedes the coding phase and the testing phase comes after the coding. A very structured approach. This is a description of a well defined process which works great on other industries but the practice proved that software development is an empiric process rather than a very well defined one and for such process you need a short feedback loop. This assumption is the foundation of the agile methodologies which are surfacing the project problems since the beginning.

Test first modelTest Driven Development and Test First Programming are reducing the gap between testing and design allowing you to adapt your design as you go. The Test First Programming assumes that you have a design upfront and you start your coding by writing tests to validate your design. The loop is only between code and tests but no for design. This is in fact a longer development cycle because the design is corrected at the later time.

Test Driven Development CycleTDD assumes that you don’t have design upfront, your design emerges with the code and you have a loop between all three key elements: Test, Design, Code. Now you can incorporate testing feedback into design and code as you go. Such a cycle should take around 30 minutes.

The four golden rules of TDD:

  • Add code on red
  • Delete code on green
  • Remove duplications
  • Fix bad names

The development cycle look likes this:

TDD cycle


Key takeaways:

TDD and junior teams myth:
Junior programmers have always less to forget and fewer bad habits. Junior teams benefit most from TDD while experience programmers have to forget some of their bad habits.

A TDD example

Let’s code and design a simple stack using TDD. I’ll present couple of steps of the process but not the final implementation. First we have to answer to the most mind boggling question of a programmer:

Who was first the test or the code?

Of course the answer is THE TEST! Remember the first step of the TDD cycle, write the simplest test that fails.

So our test class is StackTest and our first method is testEmptyStack:

import static junit.framework.Assert.assertEquals;
import org.junit.Test;

public class StackTest {
    @Test
    public void testEmptyStack() {
        assertEquals(0, stack.size());
    }
}

Now we have to make the test pass writing only the code required for that. I am going to use junit for the assert methods. Our stack will be named MStack to avoid the name collision with the java.util.Stack. My IDE is insisting in using that class.

    public void testEmptyStack() {
        MStack stack = new MStack();
        assertEquals(0, stack.size());
    }

Now let’s create the MStack, for the sake of simplicity I am going to use inner classes and in the end when the design is complete I’ll pull the code out:

public class StackTest {
    @Test
    public void testEmptyStack() {
        MStack stack = new MStack();
        assertEquals(0, stack.size());
    }
}
class MStack {
    public int size() {
        return 0;
    }
}

So we have written the code that make the test pass, looking of the code i see no duplicates or function names that have to rename to a better name. Let’s write the simple test that make the code fail. I say let’s test the “push”.

runtests-1

   @Test
    public void testPushElement() {
        MStack stack = new MStack();
        stack.push("First element");
        assertEquals(1, stack.size());
    }

Add the push method and run the test to see them fail:

junit.framework.AssertionFailedError: expected:<1> but was:<0>
	at org.bserban.tdd.StackTest.testPushElement(StackTest.java:16)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

 

Now write the simplest code that make the code pass the tests, remember the rules!

Add code on red, delete code on green!

class MStack {
    private boolean elementAdded;
    public int size() {
        if (elementAdded) {
            return 1;
        }
        return 0;
    }
    public void push(String element) {
        elementAdded = true;
    }
}
Run the tests:
Process finished with exit code 0

The code above could look stupid but this is the essence of the test driven design, remember experience programmers have a lot to forget :-), they are not willing to code in such manner until the see the true benefit of such approach. The problem we choose is trivial and for people with experience this way to code sounds silly. I’ll add two new asserts that make the code fail the tests.

    @Test
    public void testPushElement() {
        MStack stack = new MStack();
        stack.push("First element");
        assertEquals(1, stack.size());
        stack.push("Second element");
        assertEquals(2, stack.size());
        stack.push("Third element");
        assertEquals(3, stack.size());                
    }

Process finished with exit code -1

junit.framework.AssertionFailedError: expected:<2> but was:<1>
	at org.bserban.tdd.StackTest.testPushElement(StackTest.java:19)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Remember, don’t delete the existing code on red:

class MStack {
    private boolean elementAdded;
    private boolean secondElementAdded;
    private boolean thirdElementAdded;

    public int size() {
        if (thirdElementAdded) {
            return 3;
        }else if (secondElementAdded) {
            return 2;
        } else if(elementAdded){
                return 1;
        }
        return 0;
    }
    public void push(String element) {
        if (elementAdded) {
            if (secondElementAdded) {
                thirdElementAdded = true;
            } else {
                secondElementAdded = true;
            }
        } else {
            elementAdded = true;
        }
    }
}

Run the tests:
Process finished with exit code 0

Now we are on green and we are ready to refactor, the rules are: “Remove duplication and fix bad wording”. Studying the code above we see that elementAdded, secondElementAdded and thirdElementAdded is in fact a duplication, also from the size method we see that the last element added gives us the size.

So I say to refactor like this, let’s add a size variable and every time we push an element you increment this variable. Evrika! The code becomes cleaner and compact :-). I must confess that these are lines that I would be writing from the start, what a bad habit 😀

class MStack {
    private int size = 0;
    public int size() {
        return size;
    }
    public void push(String element) {
        size++;
    }
}
Run the tests:
Process finished with exit code 0

Now we proceed with method pop in the similar mater. Write the simplest test that fails:

    @Test
    public void testPopElement() {
        MStack stack = new MStack();
        stack.push("First element");
        assertEquals(1, stack.size());
        stack.pop();
        assertEquals(0, stack.size());
    }
Run the tests:
junit.framework.AssertionFailedError: expected:<0> but was:<1>
	at org.bserban.tdd.StackTest.testPopElement(StackTest.java:29)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

We are on red, we add code, we create variable popOnce:

private boolean popOnce=false;
    public int size() {
        if (popOnce) {
            return size - 1;
        }
        return size;
    }
    public void pop() {
        popOnce = true;
    }
Run the tests:
Process finished with exit code 0

Now we add the simple test that fails, three pushes and two pops. And so on, in the end we end up with the following pop code

public class StackTest {
    @Test
    public void testEmptyStack() {
        MStack stack = new MStack();
        assertEquals(0, stack.size());
    }
    @Test
    public void testPushElement() {
        MStack stack = new MStack();
        stack.push("First element");
        assertEquals(1, stack.size());
        stack.push("Second element");
        assertEquals(2, stack.size());
        stack.push("Third element");
        assertEquals(3, stack.size());
    }
    @Test
    public void testPopElement() {
        MStack stack = new MStack();
        stack.push("First element");
        assertEquals(1, stack.size());
        stack.pop();
        assertEquals(0, stack.size());
        stack.push("First element");
        stack.push("Second element");
        stack.push("Third element");
        stack.pop();
        stack.pop();
        assertEquals(1, stack.size());        
    }
}
class MStack {
    private int size = 0;
    public int size() {
        return size;
    }
    public void push(String element) {
        size++;
    }
    public void pop() {
        if (size > 0) {
            size--;
        }
    }
}
Run the tests:
Process finished with exit code 0

I think you’ve got the idea. I let you finish this implementation, I’ll just remind you the golden rules:

Cheers,

Comments

Comments are closed.