Monday, December 26, 2022

Test-Driven Development with Spring Boot Starter Test

In this example we utilise Spring Boot's utilities to test our application, particularly JUnit. The spring-boot-starter-test imports Spring Boot test modules as well as JUnit, AssertJ, Hamcrest, etc. So let's get to it.

Create the Project

Create your project using Spring Initializr. We'll be using Maven, Java 8, Spring Boot 2.7.7, and without dependencies. Before generating it, explore the POM, there should be spring-boot-starter-test in the test scpope. As for the project Metadata, you can put whatever you like for the group, artifact, name, etc. But this is what I've got:

Project Metadata

  • Group: com.blogspot.jpllosa
  • Artifact: tdd-junit
  • Name: TDD with JUnit
  • Description: Test-Driven Development with JUnit
  • Package name: com.blogspot.jpllosa

When you are ready, import the project to your IDE. I use Spring Tool Suite 4.

Test-Driven Development Coding Rythm

At the heart of TDD (Test-Driven Development) is unit testing. We rely on unit testing to prevent bugs. With unit tests, regression testing can be run many times a day. A comprehensive test prevents fixed bugs from coming back or finding side effect of changes.

In TDD, the rythm of coding is test a little... code a little... test a little... code a little...

So we follow these 5 steps:

  1. Write a test
  2. Watch it fail
  3. Write the code
  4. Run the tests
  5. Refactor

Write a Test

For this example we will create a greatest common factor finder between two numbers. Let's create a skeleton class called MathFun in /src/main/java. Now, let's follow the first step and write a test in /src/test/java, create MathFunTest. This signifies that MathFun is the class being tested. We're using JUnit 5 so annotate it with @SpringBootTest.

In order for us to write a test, we need to know what is a greatest common factor. For in depth details, google it. Here's a quick explanation. The greatest common factor (GCF) of a set of numbers is the largest factor that all the numbers share. The greatest common factor of 12 and 16 is 4. Factors of 12 are 1, 2, 3, 4, 6, and 12. Factors of 16 are 1, 2, 4, 8, and 16. Factors are numbers we multiply together to get another number. E.g. 2 X 6 = 12, so 2 and 6 are factors of 12. E.g. 4 x 4 = 16. The common factors are 1, 2, 4. Making 4 the GCF. Understood?

Okay, so our input will be 12 and 16 and the expected result is 4. Create a method testWithTwoPositiveNumbers and watch it fail. Right click + Run as JUnit test or you can go command line, "mvn test".

MathFun.java


package com.blogspot.jpllosa;

public class MathFun {

	public int getGCF(int x, int y) {
		return 0;
	}
}

MathFunTest.java


package com.blogspot.jpllosa;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MathFunTest {
	
	@Test
	public void testWithTwoPositiveNumbers( ) {
		MathFun mf = new MathFun();
		
		assertEquals(4, mf.getGCF(12, 16));
	}

}

Failure Result


org.opentest4j.AssertionFailedError: expected: <4> but was: <0>

Write the Code

Now that we have seen it fail. Time to make it pass. This is a quick one. We will use Euclid's algorithm for finding the GCF. Change the implementation to return the result of (y == 0) ? x : getGCF(y, x % y). Then run the tests again. This time you should have a green for a pssing test as shwon below. 

TDD with JUnit

Great!

Refactor

We trust Euclid's algorithm so we don't need to refator the code. This example is not about finding the GCF anyway but an exercise on TDD. The next thing would be to think of another test case that we can exercise on our getGCF method. Let's add another test, testWithZero, to test with a zero input. Run the test again. You should have two passing tests.


@Test
public void testWithZero( ) {
	MathFun mf = new MathFun();
	
	assertEquals(6, mf.getGCF(0, 6));
}

Here's how it looks on the command line:


2022-12-26 07:45:24.125  INFO 15812 --- [           main] com.blogspot.jpllosa.MathFunTest         : Started MathFunTest in 0.98 seconds (JVM running for 2.322)
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.804 s - in com.blogspot.jpllosa.MathFunTest

Brilliant! Now you are getting the hang of the rythm of coding.

There you have it. A simple TDD example. The complete project can be cloned from GitHub.