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.