Test Driven Development (TDD) is not just about writing unit tests, its a design choice.
|
![]() |
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.
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.
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.