Overview

Test Driven Development (TDD) is not just about writing unit tests, its a design choice.
The basic premise consists of 3 steps repeated until all the requirements are satisfied.


  • Write a Failing Test
  • Write the Minimum Amount Of Code to Pass the Test
  • Refactor To Remove Duplication

TDD Cycle

Write a Failing Test

Pick you first requirement and write a test case. The test must satisfy the following;

Why must the test fail? - This is important for the next step in the cycle, as this gives us instant feedback on our production code (when we write it) i.e The code works or it doesn't

Why must the code compile? - Without compiling code we cannot be sure the test fails for the right reasons

It will be necessary at this point to write some production code to get the test to compile, but only write the minimum code possible to make the test compile and then fail - No design patten no logic, just the class with the empty method

Failing (but compiling) Unit Test Minimum Production Code (to fail test)
public class BasicCalculatorTest {
    @Test
    public void shouldSubtractTwoNumbers() {
        assertEquals( 7, BasicCalculator.subtract( 10 - 3 ) );
    }
}
                                
public class BasicCalculator {
    public long subtract( int n1, int n2 ) {
        throw new UnsupportedOperationException( "Not Implemented" );

    }
}
                                

The production code is the least amount we can write to allow the test to compile. In order to make it fail, I have chosen to thrown an unsupported exception. Another valid return value could be -1, but there is a danger here that the test may pass by coincidence, for example if the test case was 10 - 11. I want to be sure the test will fail, so I am throwing an exception.

Write the Minimum Amount Of Code to Pass the Test

Whats the least amount of code we can write to pass the test?
No its not return n1 - n2;
It's return 7; Yes thats correct the minimum amount of code to pass the test is 7;

Failing Unit Test Minimum Production Code (to pass the test)
public class BasicCalculatorTest {
    @Test
    public void shouldSubtractTwoNumbers() {
        assertEquals( 7, BasicCalculator.subtract( 10 - 3 ) );
    }
}
                                
public class BasicCalculator {
    public long subtract( int n1, int n2 ) {
        return 7;

    }
}
                                

Here is the first lesson - Listen to your tests! The calculator is pretty poor if every time you subtract two numbers it always returns the same number. Listen to your test(s) - What they are saying is you have not written enough test cases! The big difference with TDD is you will write many more test cases than maybe you did previously, before you condemn it, just think about how robust and thorough your 'normal test' would be.

It would not be unheard of for code like this to have either no tests or just a simple 1 scenario happy day test case like above. TDD promotes better code and better tests and this sets up for the most powerful step, the refactoring.

Refactor To Remove Duplication

The next step is refactoring to remove any duplication. I can't see any duplication or any obvious refactoring on this small amount of code, so we can continue the cycle back to step 1

Continue the cycle

Add another test case...
Failing Unit Test Existing Production Code (unaltered should fail the test)
public class BasicCalculatorTest {
    @Test
    public void shouldSubtractTwoNumbers() {
        assertEquals( 7, BasicCalculator.subtract( 10 - 3 ) );
        assertEquals( 1, BasicCalculator.subtract( 5 - 4 ) );
    }
}
                                
public class BasicCalculator {
    public long subtract( int n1, int n2 ) {
        return 7;

    }
}
                                

In this example I can simply add another assert, as my test falls within the same concept, if I were to add numbers I would certainly create a new test method. Adding another assert with a different expected result will of course fail the test. Now whats the minimum amount of code we can write to pass the test? We can't return 1; as this will fail the 1st test (assert) - We now have a nice test regression suite building up.
So now it is appropriate to write return n1 - n2; as this is now the minimum code we can write to pass this test and to ensure all previous tests pass.

Failing Unit Test Minimum Production Code (to pass all tests)
public class BasicCalculatorTest {
    @Test
    public void shouldSubtractTwoNumbers() {
        assertEquals( 7, BasicCalculator.subtract( 10 - 3 ) );
        assertEquals( 1, BasicCalculator.subtract( 5 - 4 ) );
    }
}
                                
public class BasicCalculator {
    public long subtract( int n1, int n2 ) {
        return n1 - n2;

    }

}
                                

What was the point, what have we achieved? Two test cases are better than one. Consider the following;

// Single Test Case
assertEquals( 0, BasicCalculator.subtract( 10 - 10 );

// Production Code, with typo
return n2 - n2;
                    
There is a typo here, n2 - n2, in place of n1 - n2.
Here's the real problem - The test passes and gives 100% code coverage!
We have a false sense of security that the code is good, but we have failed to test more than a single test case.

Using TDD we can help protected against tests passing by coincidence by coding in a more structured way, provide a more robust suite of regression tests to enable refactoring or future changes to be made with more confidence and more chance of success first time.