A step by step introduction to Test Driven Development in Java.
Note: There is a newer version of this article in JavaScript.
Background
This following exercise is based on a TDD workshop that I conducted for a client.
Exercise
I am going to demonstrate TDD by completing FizzBuzz. I have chosen to show each step in Java because most of my work so far has been in this language. However, the same concepts apply to any object-oriented language. The complete source code can be found on Github in Java or JavaScript.
The exercise is complete when the following input:
[1, 2, 3, 5, 6, 10, 15, 30]
results in the following output:
"1, 2, Fizz, Buzz, Fizz, Buzz, FizzBuzz, FizzBuzz"
Things to keep in mind
When demonstrating this exercise I like to mention the following points:
- Don’t write any production code before you have a failing test (including compile errors!)
- Make each step as small and simple as possible.
- Commit as soon as you have a passing test.
Implementation
Here is the starter code for the test:
public class FizzBuzzTest {
@Test
public void test() {
}
}
Make sure the test is green!
Here is the starter code for the app:
public class FizzBuzz {
public void execute() {
}
}
Make sure the test is still green!
This is where my initial commit ends.
The first test can be written as follows:
@Test
public void shouldProcessInput() {
FizzBuzz fizzBuzz = new FizzBuzz();
String output = fizzBuzz.processNumber(1);
assertThat(output, is("1"));
}
Before running the test, you will need to fix the compile errors. I would recommend doing this line per line. After each line, you should also make sure the test is still passing. Once the compile errors are removed the test will fail. The following snippet will make the test pass:
public class FizzBuzz {
public void execute() {
}
public String processNumber(int number) {
return "1";
}
}
How easy was that! Don’t forget to commit ;).
Below, I rename the test and inline the method call:
@Test
public void shouldProcessNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.processNumber(1), is("1"));
}
Make sure the test is green and then commit!
I then add another assert statement to the test and update the code:
@Test
public void shouldProcessNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.processNumber(1), is("1"));
assertThat(fizzBuzz.processNumber(2), is("2"));
}
public String processNumber(int number) {
return String.valueOf(number);
}
I’m going to stop reminding you about committing from now on.
Here I add functionality for Fizz when the number is 3:
@Test
public void shouldProcessNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.processNumber(1), is("1"));
assertThat(fizzBuzz.processNumber(2), is("2"));
assertThat(fizzBuzz.processNumber(3), is("Fizz"));
}
public String processNumber(int number) {
if (number == 3) {
return "Fizz";
}
return String.valueOf(number);
}
I do the same for Buzz when the number is 5:
@Test
public void shouldProcessNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.processNumber(1), is("1"));
assertThat(fizzBuzz.processNumber(2), is("2"));
assertThat(fizzBuzz.processNumber(3), is("Fizz"));
assertThat(fizzBuzz.processNumber(5), is("Buzz"));
}
public String processNumber(int number) {
if (number == 3) {
return "Fizz";
}
if (number == 5) {
return "Buzz";
}
return String.valueOf(number);
}
Here I add functionality for Fizz if the number is a multiple of 3:
@Test
public void shouldProcessNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.processNumber(1), is("1"));
assertThat(fizzBuzz.processNumber(2), is("2"));
assertThat(fizzBuzz.processNumber(3), is("Fizz"));
assertThat(fizzBuzz.processNumber(5), is("Buzz"));
assertThat(fizzBuzz.processNumber(6), is("Fizz"));
}
public String processNumber(int number) {
if (number % 3 == 0) {
return "Fizz";
}
if (number == 5) {
return "Buzz";
}
return String.valueOf(number);
}
The same for Buzz:
@Test
public void shouldProcessNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.processNumber(1), is("1"));
assertThat(fizzBuzz.processNumber(2), is("2"));
assertThat(fizzBuzz.processNumber(3), is("Fizz"));
assertThat(fizzBuzz.processNumber(5), is("Buzz"));
assertThat(fizzBuzz.processNumber(6), is("Fizz"));
assertThat(fizzBuzz.processNumber(10), is("Buzz"));
}
public String processNumber(int number) {
if (number % 3 == 0) {
return "Fizz";
}
if (number % 5 == 0) {
return "Buzz";
}
return String.valueOf(number);
}
Here I add FizzBuzz functionality:
@Test
public void shouldProcessNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.processNumber(1), is("1"));
assertThat(fizzBuzz.processNumber(2), is("2"));
assertThat(fizzBuzz.processNumber(3), is("Fizz"));
assertThat(fizzBuzz.processNumber(5), is("Buzz"));
assertThat(fizzBuzz.processNumber(6), is("Fizz"));
assertThat(fizzBuzz.processNumber(10), is("Buzz"));
assertThat(fizzBuzz.processNumber(15), is("FizzBuzz"));
}
public String processNumber(int number) {
if (number % 3 == 0 && number % 5 == 0) {
return "FizzBuzz";
}
if (number % 3 == 0) {
return "Fizz";
}
if (number % 5 == 0) {
return "Buzz";
}
return String.valueOf(number);
}
Here I extract isMultipleOf3(...)
and isMultipleOf5(...)
:
public String processNumber(int number) {
if (isMultipleOf3(number) && isMultipleOf5(number)) {
return "FizzBuzz";
}
if (isMultipleOf3(number)) {
return "Fizz";
}
if (isMultipleOf5(number)) {
return "Buzz";
}
return String.valueOf(number);
}
private boolean isMultipleOf5(int number) {
return number % 5 == 0;
}
private boolean isMultipleOf3(int number) {
return number % 3 == 0;
}
I then add a test case for execute(...)
and make it green:
@Test
public void shouldExecute() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.execute(new int[]{1}), is("1"));
}
public String execute(int[] numbers) {
return processNumber(numbers[0]);
}
The final test:
@Test
public void shouldExecute() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertThat(fizzBuzz.execute(new int[]{1}), is("1"));
assertThat(fizzBuzz.execute(new int[]{1, 2, 3, 5, 6, 10, 15, 30}),
is("1, 2, Fizz, Buzz, Fizz, Buzz, FizzBuzz, FizzBuzz"));
}
The final refactoring:
public String execute(int[] numbers) {
return Arrays.stream(numbers)
.mapToObj(this::processNumber)
.collect(Collectors.joining(", "));
}
Final Thoughts
That’s the end of the exercise. I hope you enjoyed it and were able to learn something new. The most important take-away from this exercise is to take small steps! In case you missed the link at the beginning, the complete source code can be found on Github.
Timeline:
- January 2018: First published
- May 2020: Rewrite in JavaScript