STS 4.0.1 (Spring Boot):
Practicing TDD (Test Driven Development)
Opening note. Most people can make the most of their potential by following these two simple rules: 1. Before writing any code, write a failing automated test. 2. Remove duplication. | |
Requirements and improvements list - $5 + 10CHF = $10 (if the exchange rate is 2:1) - $5 x 2 = $10 - Make amount private - Dollar side effects? - Money rounding? - New: equals( ) Implement the equality feature <- Goal for this example -New: hashCode( ) -New: Equal null -New: Equal object | Purpose. Understanding the rhythm of TDD 1. Quickly add a single test. 2. Run all tests and confirm the new one fails. 3. Make a small change to the code. 4. Run all tests and confirm they all pass. 5. Refactor to remove duplication. |
A leftover note from the previous chapter
Turning a feeling (a disgust toward a side effect) into a test (multiplying on a single Dollar object twice) is a general theme of TDD.
Chapter 3. Equality for All
3-0. Recognizing the problem
1) The worst bug in the author's experience was that changing the value on the second check also changed the value on the first — an aliasing problem. To fix this, you need to use value objects. One of the constraints of a value object is that the object's instance variables, once set in the constructor, never change — so if you use value objects, you don't need to worry about aliases. ( The "alias" the author mentions I take to mean variable or method names. ) 2) But actually, this way of using an object as a value — like the Dollar object we wrote — is the Value Object Pattern. A Value Object is conceptually a very small and simple object. But it's an essential element of Domain-Driven Design and object-oriented programming. Value objects are defined by the rule that the equality of two value objects is not based on identity, but on their content. (the equality of two Value Objects is not based on identity) Equality has two operators: '==' and '===.' '==' is for equality testing, and on scalar values, strings, or integers, it checks whether the values are the same. '===' on the other hand, when used on two objects, tells you whether the two objects are actually the same. Two objects being "the same" means they share the same memory location — so if one of them is changed by some operation, the other is affected too. Source: A Penguin Living in an Igloo 2) In short, the goal for this chapter is to implement the equality feature so we can compare Dollar to Dollar directly. Along the way we'll use the triangulation strategy. Triangulation: if a radio signal is being detected at two receivers, the distance between the receivers is known, and the direction of the signal at each receiver is known, that's enough information to figure out the distance and bearing of the signal. |
3-1. Run the test first.
package com.noramlstory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestTdd2019 {
@Test
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product;
product = five.times(2);
assertEquals(10, product.amount);
product = five.times(3);
assertEquals(15, product.amount);
}
// 3장. 모두를 위한 평등
@Test
public void testEquality() {
// 3-1. 일단 테스트를 진행한다. $5 == $5 ( $5 같다 $5 )
assertTrue(new Dollar(5).equals(new Dollar(5)));
}
}
class Dollar{
int amount;
Dollar(int amount){
this.amount = amount;
}
Dollar times(int multiplier) {
return new Dollar(amount * multiplier);
}
}
3-2 Make it work, even if you have to fake it.
package com.noramlstory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestTdd2019 {
@Test
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product;
product = five.times(2);
assertEquals(10, product.amount);
product = five.times(3);
assertEquals(15, product.amount);
}
// 3장. 모두를 위한 평등
@Test
public void testEquality() {
// 3-1. 일단 테스트를 진행한다.
assertTrue(new Dollar(5).equals(new Dollar(5)));
}
}
class Dollar{
int amount;
Dollar(int amount){
this.amount = amount;
}
Dollar times(int multiplier) {
return new Dollar(amount * multiplier);
}
// 3-2 일단 가짜로 라도, 돌아가게 만든다.
public boolean equals(Object object) {
return true;
}
}
3-3 Adding an opposite comparison for triangulation..
package com.noramlstory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestTdd2019 {
@Test
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product;
product = five.times(2);
assertEquals(10, product.amount);
product = five.times(3);
assertEquals(15, product.amount);
}
// 3장. 모두를 위한 평등
@Test
public void testEquality() {
// 3-1. 일단 테스트를 진행한다. $5 == $5 ( $5 같다 $5 )
assertTrue(new Dollar(5).equals(new Dollar(5)));
// 3-3. 삼각 측량을 위해 반대? 비교를 하나 추가 해본다. $5 != $6 ( $5 같지않다 $6 )
assertFalse(new Dollar(5).equals(new Dollar(6)));
}
}
class Dollar{
int amount;
Dollar(int amount){
this.amount = amount;
}
Dollar times(int multiplier) {
return new Dollar(amount * multiplier);
}
// 3-2 일단 가짜로 라도, 돌아가게 만든다.
public boolean equals(Object object) {
return true;
}
}
3-4 Generalize the equality. -> Implement the equality feature via the equals() method
package com.noramlstory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestTdd2019 {
@Test
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product;
product = five.times(2);
assertEquals(10, product.amount);
product = five.times(3);
assertEquals(15, product.amount);
}
// 3장. 모두를 위한 평등
@Test
public void testEquality() {
// 3-1. 일단 테스트를 진행한다. $5 == $5 ( $5 같다 $5 )
assertTrue(new Dollar(5).equals(new Dollar(5)));
// 3-3. 삼각 측량을 위해 반대? 비교를 하나 추가 해본다. $5 != $6 ( $5 같지않다 $6 )
assertFalse(new Dollar(5).equals(new Dollar(6)));
}
}
class Dollar{
int amount;
Dollar(int amount){
this.amount = amount;
}
Dollar times(int multiplier) {
return new Dollar(amount * multiplier);
}
// 3-2 일단 가짜로 돌아가게 만든다.
// public boolean equals(Object object) {
// return true;
// }
// 3-4 동치성을 일반화 한다
public boolean equals(Object object) {
Dollar dollar = (Dollar) object;
return amount == dollar.amount;
}
}
3-5 What changed compared to the previous example, with some personal commentary.
Before, we compared the expected value with Dollar(5)
assertEquals(10 , product.amount);
Today, we compared Dollar(5) with Dollar(n).
assertTrue(new Dollar(5) .equals(new Dollar(5)));
The fancy? word "equivalence" came out, but in the end it's a term introduced to distinguish living in the same place == from being the same entity ===.
The former is when the address (the variable) is the same; the latter means the address and the value are the same.
Something like the difference between people who share a name and the same person, maybe?
That's a good way to think about it.
Quick detour ~ ;D There's a similar example in Spring — leaving a link for reference ~ -> Following Toby's Spring 3.1 in Spring Boot: Ch.1 - 1.6 Singleton Registry and Object Scope |
