Passionate Development From Journeyman to Master

Putting TDD into Practice

A couple of weeks ago, I was given a couple of programming tasks to complete.

When I looked at the tasks’ description - I realised Test Driven Development (TDD would be a good fit to approach them. Another plus for me is, I have never used TDD before - although I have been reading and hearing it quite a lot, so it is a good opportunity to give it a try.

One of the tasks is to write a cheque writer program - a program that converts a dollar value in number to its text representation (if it were written for a cheque), for example: given an input 2.50, the output should be a string “two DOLLARS and fifty CENTS”.

I chose to implement the solution in Java since I’m quite comfortable with it. And I am using JUnit to assist with TDD.

By employing TDD - I was writing my unit tests BEFORE I wrote the implementation.

Below is a snippet of my JUnit test (the first time I wrote JUnit test for a long time - so definitely can be done better): ``` java package org.tjandrawibawa.testcase;

import org.junit.Test ; import static org.junit.Assert.* ; import org.tjandrawibawa.task.*;

public class TestChequeWriter { ChequeWriter writer = new ChequeWriter();

@Test
public void testSimple() {
	System.out.println("Test input: 1");
	assertTrue( writer.write("1").equals("one DOLLARS"));
}

@Test
public void testSimpleDecimal() {
	System.out.println("Test input: 41.01");
	assertTrue( writer.write("41.01").equals("forty one DOLLARS AND one CENTS"));
}

@Test
public void testHundreds() {
	System.out.println("Test input: 151.59");
	assertTrue( writer.write("151.59").equals("one hundred and fifty one DOLLARS AND fifty nine CENTS"));
}


/* BEGIN: Boundary Tests */
@Test
public void testBoundary1() {
	System.out.println("Test input: 99.99");
	assertTrue( writer.write("99.99").equals("ninety nine DOLLARS AND ninety nine CENTS"));
}

@Test
public void testBoundary2() {
	System.out.println("Test input: 100.99");
	assertTrue( writer.write("100.99").equals("one hundred DOLLARS AND ninety nine CENTS"));
}

@Test
public void testBoundary3() {
	System.out.println("Test input: 999.99");
	assertTrue( writer.write("999.99").equals("nine hundred and ninety nine DOLLARS AND ninety nine CENTS"));
}

@Test
public void testBoundary4() {
	System.out.println("Test input: 1000.99");
	assertTrue( writer.write("1000.99").equals("one thousand DOLLARS AND ninety nine CENTS"));
}

@Test
public void testBoundary6() {
	System.out.println("Test input: 999999.99");
	assertTrue( writer.write("999999.99").equals("nine hundred and ninety nine thousand, nine hundred and ninety nine DOLLARS AND ninety nine CENTS"));
}

@Test
public void testBoundary7() {
	System.out.println("Test input: 1000000.99");
	assertTrue( writer.write("1000000.99").equals("one million DOLLARS AND ninety nine CENTS"));
}

@Test
public void testBoundary8() {
	System.out.println("Test input: 9999999.99");
	assertTrue( writer.write("999999999.99").equals("nine hundred and ninety nine million, nine hundred and ninety nine thousand, nine hundred and ninety nine DOLLARS AND ninety nine CENTS"));
}

@Test
public void testBoundary9() {
	System.out.println("Test input: 10000000.99");
	assertTrue( writer.write("1000000000.99").equals("one billion DOLLARS AND ninety nine CENTS"));
}

/* END: Boundary Tests */

/* BEGIN: Test failures */
@Test
public void testAmountNegative() {
	System.out.println("Test input: -1");
	assertTrue( writer.write("-1").equals("-1 is invalid - valid amount is between 0 to 2 billions dollars (inclusive)"));
}

@Test
public void testAmountGreaterThanTwoBillion() {
	System.out.println("Test input: 2000000001");
	assertTrue( writer.write("2000000001").equals("2000000001 is invalid - valid amount is between 0 to 2 billions dollars (inclusive)"));
}

@Test
public void testDecimalPrecision() {
	System.out.println("Test input: 1.222");
	assertTrue( writer.write("1.222").equals("1.222 is invalid - only up to 2 decimal places is accepted"));
}

@Test
public void testNonNumberInput1() {
	System.out.println("Test input: 1.22.2");
	assertTrue( writer.write("1.22.2").equals("1.22.2 is not a valid number format"));
}

@Test
public void testNonNumberInput2() {
	System.out.println("Test input: xx");
	assertTrue( writer.write("xx").equals("xx is not a valid number format"));
}
/* END: Test failures */

} ```

The steps that I went through to solve the task:

  • Scribble the solution in paper. I found this practice generally helps my thinking a lot.
  • I then write the skeleton for the solution - which is mainly a class with methods that don't do anything as yet.
  • I then write unit tests for the main method in JUnit.
  • I then write the implementation, run the test and repeat until I got all green (all tests passed)

I found that this approach worked very well for this particular task. Especially the boundary tests exposed the wrong approach that I took when developing the solution.

Refactoring has also become very easy as well.

While I am not convinced writing tests first and code the implementation to the test is applicable and beneficial all the time. One thing that I am sold of is: having test coverage is awesome. Particularly with refactoring - there is no way that you can confidently refactor without having a good enough test coverage.

general