Back to feed
Renewal·서른의 생활코딩

TDD Practice in Spring Boot — Chapter 1: A Money Object That Supports Multiple Currencies

NS
normalstory
cover image

Practicing TDD (Test Driven Development) 

in STS 4.0.1 (Spring Boot) Test Driven Development


Test Driven Development (TDD) was a book I read.  

Also from last year... and I'm only now posting about it. For now I'll post only the Java part. It's shorter than I thought, so it should be fine to write alongside other posts. 

The book tells a fictional story: a company owns a program called WhyCash and a client asks for some feature changes (adding multi-currency support). The project is carried out by applying TDD and iteratively improving the code.  

Alright, let's begin. My TDD test environment is as follows.

- OS : mac

- STS : 4.0.1

Build : Gradle

Framework : JUnit 




Prologue >

 1. ~The author makes a subtle joke:

If you're a genius, you don't need these rules. If you're a fool, these rules won't help you either. But the majority of people in between can fully unleash their potential by following these two simple rules:

- Before writing any code, write a failing automated test.

- Remove duplication.


 2. Before the example, the aim is to observe the rhythm of test-driven development:

1. Quickly add a test.

2. Run all tests and see the new one fail.

3. Make a small change.

4. Run all tests and see them all pass.

5. Remove duplication by refactoring. (Footnote: refactoring means changing the internal structure of code without changing its external behavior.)  A reference blog 

 



Chapter 1. A Money object that supports multiple currencies 



1. Create a project.
1) Spring Starter Project With Spring Boot, even an arbitrary empty project comes with a JUnit-ready environment by default. Since we're doing TDD, let's start with the setup that lets us test fastest. 

2) Enter the settings — I personally chose Gradle. 

3) For this section's options I didn't pick anything. 


2. Once the project is created

1) Open the Test class under the test subfolder.

2) First-round requirement:

       

       $5 + 10CHF = $10 (when the FX rate is 2:1)

       $5 x 2 = $10                              <-- let's solve this first,

Making amount private 

Dollar side effects?

Money rounding? 


 Code: a simple multiplication example.  <-- This code becomes the starting benchmark for debugging and refactoring.

public class TddStudy2019ApplicationTests {
	@Test
	public void testMultiplication() {
		Dollar five = new Dollar(5);
		five.times(2);
		assertEquals(10,five.amount);
	}
}

(1) After writing the first example and running Run As > JUnit, an error popup appears — ignore it. It's because the Dollar class isn't declared yet. The plan is to test first, see the error, and resolve it afterward — part of the TDD flow.

(2) A red bar appears. As expected, the run result is an error. Let's check the error message in the bottom-right.

- There's no Dollar class.


2) Improve the code to address the error.

(1) For now I declared Dollar as an inner class.


public class TddStudy2019ApplicationTests { @Test public void testMultiplication() { Dollar five = new Dollar(5); five.times(2); assertEquals(10,five.amount); } } class Dollar{ }

(2) A red bar appears again. This time the error message changed and a few red squiggles popped up in the test code. But don't panic — it means what to fix is now clearer. 

- No constructor                       The constructor Dollar(int) is undefined

- No times(int) method       The method times(int) is undefined for the type Dollar

- No amount field              amount cannot be resolved or is not a field


3) Apply the updated error messages.

(1) Improve the code again.

- Add a constructor Dollar(int amount)

- Add a times(int) method times(int multiplier),   <-- the book phrases it as 'implementing a times() stub.' (Footnote: a stub implementation means writing just the method signature (and a return statement if it has a return type) so that the (test) code that calls it can compile — an empty shell. In short, 'stub' means a mock implementation: a simple fake that satisfies the interface and can be used in tests.) + Related link 

- Add an amount field int amount =10;.

 public class TddStudy2019ApplicationTests {
	@Test
	public void testMultiplication() {
		Dollar five = new Dollar(5);
		five.times(2);
		assertEquals(10,five.amount);
	}
}

class Dollar{
	Dollar(int amount){
	}
	
	void times(int multiplier) {	
	}
	
	int amount =10;
}
(2) Run it again — a green bar. Success.


3. The first example succeeded.

1) But there's a lot to improve. It parallels the first example in my recent 'Following Toby 3.1 in Spring Boot: Chapter 1 - 1.1 Dreadful DAO' post . Get it running first, then improve the code — that's the book's overall rhythm.

2) Don't forget the generalization cycle before continuing tests.

(1) Add a small test.   Done 

(2) Run all tests and see the new one fail.   Done 

(3) Make a small fix.   Done 

(4) Run all tests and see them pass.   Done 

(5) Refactor to remove duplication.

If dependency is the problem, duplication is a symptom. Unlike real life — where merely removing the symptom while leaving the cause untouched reveals the problem in the worst places — in a program, removing duplication removes the dependency. Removing duplication before moving to the next test maximizes the chance that one — and only one — code change makes the next test pass.


4. Refactoring. Now to remove the duplication.

1) What can we tweak? Honestly the first example is the author's dramatic setup — the code is a bit odd. Improving those oddities is exactly what the rest of the book does, so don't take it too cynically >< Anyway, the example above is effectively the same as the code below.

In the screenshot, since amount is already 10, I removed the seemingly unnecessary (or confusing) method argument first. That yields assertEquals(10, amount); as the result. Even with the duplication removed it still runs. 

But in the story's setup we needed to multiply $5 by 2 — we were just trying to get that test green first, which is why the code looks the way it does. In a sense the code in the screenshot looks more 'correct,' but in real life that kind of requirement wouldn't exist, which is probably why the author chose the example's code. So going forward I'll improve things starting from the code marked '<-- this code becomes the starting benchmark for debugging and refactoring.


2) With that in mind, first change int amount = 10; to amount = 5 * 2; .

class Dollar{
	int amount;
	
	Dollar(int amount){
	}
	
	void times(int multiplier) {
		amount = 5 * 2;
	}
}


3) The rest can be tweaked as below. Both '2)' and '3)' produce a green bar.

class Dollar{
	int amount;
	
	Dollar(int amount){
		this.amount = amount;
	}
	
	void times(int multiplier) {
		amount = amount * multiplier;
	}
}


4) And that completes one full test cycle.

(1) Add a small test.   Done 

(2) Run all tests and see the new one fail.  Done 

(3) Make a small fix.  Done 

(4) Run all tests and see them pass.  Done 

(5) Refactor to remove duplication.  Done 

This English version was translated by Claude.

친절한 찰쓰씨
Written by
친절한 찰쓰씨

Pleasant Charles — UI/UX researcher at AIT. Keeping notes on design, planning, and slow days here since 2010.

More on the author's page

Keep reading

Renewal

Steadily, for the long haul, without burning out

Mar 31, 2026·9 min
Renewal

Tech-life balance

Feb 7, 2026·3 min
Renewal

Humanality, by Park Jeong-ryeol

Feb 7, 2026·11 min