Social Icons

Pages

Wednesday 17 August 2016

An Attempt at Test Driven Development

"Let the tests drive the development" - Writing tests before code may sound weird at first but it does lead to a better code design, code with rich tests and a better software in the end.

Test Driven Development is based on the following ideas -
  1. Write a failing test case before writing production code
  2. Do not write any production code more than what is sufficient to pass a failing test case
  3. Do not write any test case more than what is sufficient to fail a running production code
TDD Life-cycle
Let's build a simple "Calculator" service which will provide a functionality to add two numbers.
In "Test First Development" we would write a simple failing test before we write any production code.
STEP 1:
@Test
public void addPositiveIntegersExpectValidSummation(){
   int sum = new CalculatorService().add(2, 10);
   Assert.assertEquals (12, sum);
}
At this stage this case will fail with a compilation error "CalculatorService is undefined".
So, we have written a failing a test case (Compilation error is also a failing test case), we can proceed further to write our actual code.
STEP 2:
public class CalculatorService{
   public int add(int op1, int op2) {
      return 0;
   }
}
This code is sufficient to clear the compilation error. Let's attempt to run the test case. It fails. No surprises there.
"Expected output = 12, obtained 0".
We now have a reason to change the code. But the code change should be minimum, just enough to pass the failing test case.
STEP 3:
public class CalculatorService{
   public int add(int op1, int op2) {
      return 12;
   }
}
Let's run the test case again. It passes this time. But, the solution which we have provided for the add method is is not going to work for all the cases.
Shall we change the code at this stage ? Well the answer is NO, (Based on 2nd principle of TDD).
We need to find a reason to change the code. Do we have a failing test at this stage ? The answer is NO. So we need to have one.
STEP 4:
@Test
public void addNegativeIntegersExpectValidSummation(){
   int sum = new CalculatorService().add(-2, 10);
   Assert.assertEquals (8, sum);
}
After we are done with the above step, let's attempt to run the test case.
It fails. "Expected output = 8, obtained 12". Since, we have a failing test case, we can proceed to change the code.
STEP 5:
public class CalculatorService{
   public int add(int op1, int op2) {
      return op1 + op2;
   }
}
Let's run the test cases again. This time both of them work and we are done with the functionality as well.
Let's look back at the tests and see if we can do any form of refactoring -
STEP 6:
@Test
public void addPositiveIntegersExpectValidSummation(){
   int sum = new CalculatorService().add(2, 10);
   Assert.assertEquals(12, sum);
}
@Test
public void addNegativeIntegersExpectValidSummation(){
   int sum = new CalculatorService().add(-2, 10);
   Assert.assertEquals(8, sum);
}
Duplication, right ? Well, we should get away with duplicate code (DRY - DO NOT REPEAT YOURSELF).
private CalculatorService calculatorService;
@Before
public void init(){
   calculatorService = new CalculatorService();
}
@Test
public void addPositiveIntegersExpectValidSummation(){
   int sum = calculatorService.add(2, 10);
   Assert.assertEquals(12, sum);
}
@Test
public void addNegativeIntegersExpectValidSummation(){
   int sum = calculatorService.add(-2, 10);
   Assert.assertEquals(8, sum);
}
Looks OK, appears to be following AAA principle (Arrange, Act and Assert). Let's get away with the temporary variable sum, it does not seem to be adding too much value.
STEP 7:
@Before
public void init(){
   calculatorService = new CalculatorService();
}
@Test
public void addPositiveIntegersExpectValidSummation(){
   Assert.assertEquals(12, calculatorService.add(2, 10));
}
@Test
public void addNegativeIntegersExpectValidSummation(){
   Assert.assertEquals(8, calculatorService.add(-2, 10));
}
We are done, finally!! Let's run the cases again to see them run successfully.
Successful implementation of TDD depends on -
  1. Writing a failing test before the actual code
  2. Writing the code just enough to pass the failing test
  3. Running all the tests after the code is changed
  4. Writing a new test after all the existing test cases have passed
  5. Refactoring after the test cases have passed
  6. Re-run all test cases after refactoring
How does TDD benefit us ?
  1. TDD ensures that we take small steps in development which is in line with the idea of Baby Steps in Extreme programming
  2. It lets the developers perform continuous refactoring, leading to a better code design
  3. TDD results in rich test cases
  4. Since TDD builds the software in small steps, it leads to continuous evaluation of the code changes. Any test case failure will be identified very early in the development cycle
  5. TDD does take anxiety away
To summarize, in TDD, test code and production code go hand in hand but test code precedes the production code.

No comments:

Post a Comment