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):

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:

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.