CodeRetreat


Today is about learning & practicing


Today is about learning & practicing
What is automated testing?
-
A brief code snippet to verify that other codes behave as expected
-
Automatic, repeatable
-
Fast & deterministic
-
Code's test is the test, the test's test is the code

Test-Driven Development



A goal is Clean Code!
-
Don’t write a line of new code unless you first have a failing automated test
-
Eliminate duplication
Two rules of TDD
by Kent Beck

-
Write a test…
-
Make it run…
-
Make it right…
A goal is loose coupling!
-
You are not allowed to write any production code unless it is to make a failing unit test pass.
-
You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
-
You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
Three rules of TDD
by Uncle Bob Martin

A goal is documenting!
-
Write a test for the next bit of functionality you want to add
-
Write the functional code until the test passes
-
Refactor both new and old code to make it well structured
Three rules of TDD
by Martin Fowler

A goal is easy refactoring!
About TDD
by James Shore



A goal is fast cycles via small steps!
Test Driven Development
by Draven
A goal is planning and
experimenting in the code
A goal is to build trust
in the codebase

GIMME
EXAMPLE
String Calculator Kata
int add(String numbers)
add("") -> 0
add("4") -> 4
add("1,2") -> 3
add("1,2,3") -> 6
add("1\n2,3") -> 6 (newline is also a separator)
add("//;\n1;2") -> 3 (custom delimiter)The task
String Calculator Kata
public class StringCalculatorTest {
@Test
void shouldReturnZeroForEmptyString() {
StringCalculator calculator = new StringCalculator();
assertEquals(0, calculator.add(""));
}
}Compilation Error
(The class and method don't exist yet)
❌
RED STATE
String Calculator Kata
public class StringCalculatorTest {
@Test
void shouldReturnZeroForEmptyString() {
StringCalculator calculator = new StringCalculator();
assertEquals(0, calculator.add(""));
}
}public class StringCalculator {
public int add(String numbers) {
return 0; // Hardcoded to pass the test
}
}✅
GREEN STATE
public class StringCalculatorTest {
@Test
void shouldReturnZeroForEmptyString() {
StringCalculator calculator = new StringCalculator();
assertEquals(0, calculator.add(""));
}
@Test
void shouldReturnNumberForSingleNumber() {
StringCalculator calculator = new StringCalculator();
assertEquals(1, calculator.add("1"));
}
}✅
❌
String Calculator Kata
RED STATE
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
return Integer.parseInt(numbers);
}@Test
void shouldReturnNumberForSingleNumber() {
StringCalculator calculator = new StringCalculator();
assertEquals(1, calculator.add("1"));
}✅
String Calculator Kata
GREEN STATE
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
return Integer.parseInt(numbers);
}@Test
void shouldReturnSumOfTwoNumbers() {
StringCalculator calculator = new StringCalculator();
assertEquals(3, calculator.add("1,2"));
}String Calculator Kata
❌
RED STATE
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
String[] parts = numbers.split(",");
int sum = 0;
for (String part : parts) {
sum += Integer.parseInt(part);
}
return sum;
}@Test
void shouldReturnSumOfTwoNumbers() {
StringCalculator calculator = new StringCalculator();
assertEquals(3, calculator.add("1,2"));
}String Calculator Kata
✅
GREEN STATE
import java.util.Arrays;
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
return Arrays.stream(numbers.split(","))
.mapToInt(Integer::parseInt)
.sum();
}@Test
void shouldReturnSumOfTwoNumbers() {
StringCalculator calculator = new StringCalculator();
assertEquals(3, calculator.add("1,2"));
}String Calculator Kata
✅
BLUE STATE
@Test
void shouldReturnSumWithNewlineDelimiter() {
StringCalculator calculator = new StringCalculator();
assertEquals(6, calculator.add("1\n2,3"));
}String Calculator Kata
❌
RED STATE
import java.util.Arrays;
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
return Arrays.stream(numbers.split(","))
.mapToInt(Integer::parseInt)
.sum();
}@Test
void shouldReturnSumWithNewlineDelimiter() {
StringCalculator calculator = new StringCalculator();
assertEquals(6, calculator.add("1\n2,3"));
}String Calculator Kata
import java.util.Arrays;
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
return Arrays.stream(numbers.split(",|\n"))
.mapToInt(Integer::parseInt)
.sum();
}✅
GREEN STATE
@Test
void shouldReturnSumWithCustomDelimiter() {
StringCalculator calculator = new StringCalculator();
assertEquals(3, calculator.add("//;\n1;2"));
}String Calculator Kata
import java.util.Arrays;
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
return Arrays.stream(numbers.split(",|\n"))
.mapToInt(Integer::parseInt)
.sum();
}❌
RED STATE
String Calculator Kata
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
String delimiter = ",|\n";
String numbersWithoutDelimiter = numbers;
if (numbers.startsWith("//")) {
int delimiterIndex = numbers.indexOf("\n");
// Extract custom delimiter (e.g., ";")
delimiter = numbers.substring(2, delimiterIndex);
// Extract the actual numbers part
numbersWithoutDelimiter = numbers.substring(delimiterIndex + 1);
}
return Arrays.stream(numbersWithoutDelimiter.split(delimiter))
.mapToInt(Integer::parseInt)
.sum();
}GREEN STATE
String Calculator Kata
public int add(String input) {
if (input.isEmpty()) { return 0; }
String delimiter = determineDelimiter(input);
String numbers = getNumbersWithoutHeader(input);
return Arrays.stream(numbers.split(delimiter))
.mapToInt(Integer::parseInt)
.sum();
}
private String determineDelimiter(String input) {
return input.startsWith("//")
? Pattern.quote(input.substring(2, input.indexOf("\n")))
: ",|\n";
}
private String getNumbersWithoutHeader(String input) {
return input.startsWith("//")
? input.substring(input.indexOf("\n") + 1)
: input;
}
BLUE STATE
Coding Rules for Today
-
Strictly follow TDD rules and do fast cycles
-
Don't forget refactoring
-
As delivery doesn't matter, focus on readable Clean Code
-
Test names and bodies must document the codebase
-
There is no need for external dependencies, mocking, creating UI, etc.
-
Do NOT use AI tools Today









TDD RULZ
Coderetreat














TDD RULZ
Coderetreat
Game Of Life

Game Of Life

-
Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
-
Any live cell with more than three live neighbours dies, as if by overcrowding.
-
Any live cell with two or three live neighbours lives on to the next generation.
-
Any dead cell with exactly three live neighbours becomes a live cell.
SESSION #1
SESSION #1
No constraint, just do
TDD and Clean Code
SESSION #2
Good tests
document the code
-
Test cases against behaviour
-
Test naming
-
Include the method under testing
-
Add context, preconditions
-
Write what will happen, the expected behaviour
-
@Test
void add_Given1And2_Returns3() {
assertEquals(3, add(1, 2));
}@Test
void add_GivenTwoNumbers_ReturnsSum() {
assertEquals(1, add(0, 1));
assertEquals(3, add(1, 2));
}Bad:
Better:
describe("Math", () => {
describe("#add", () => {
it("should return 3 if 1 and 2 are given", () => {
expect(add(1,2)).toEqual(3)
describe("Math", () => {
describe("#add", () => {
it("should return the sum of two numbers given", () => {
expect(add(0,1)).toEqual(1)
expect(add(1,2)).toEqual(3)@Nested
class GivenBalanceIs1000 {
@Nested
class WhenMakingDepositOf100 {
@Test
void thenExpectBalanceToBe1100() {
BankAccount account = new BankAccount(1000);
account.deposit(100);
assertEquals(1100, account.getBalance());
}
}
}Bad:
Better:
@Nested
class Deposit {
@Nested
class GivenInitialBalance {
@Test
void addsDepositToBalance() { ... }
}
}describe("Given the balance is 1.000 €", () => {
describe("When making a deposit of 100 €", () => {
it("Then I expect the balance to be 1.100 €", () => ...describe("Given an initial balance in the account", () => {
describe("When making a deposit", () => {
it("Should add the deposit to the balance", () => ...Bad:
Better:
SESSION #2
Constraints:
-
TDD Ping-Pong
-
Behavior in test names
-
Bonus: infinite table
SESSION #3
TDD Strategies
Obvious Implementation
Fake It ('til you make it)
Triangulation
TDD Strategies
Obvious Implementation
@Test
void getSquareArea_GivenEdgeLength_ReturnsArea() {
// Arrange
int edgeLength = 3;
// Act
int area = getSquareArea(edgeLength);
// Assert
assertEquals(9, area);
}int getSquareArea(int edgeLength) {
return edgeLength * edgeLength;
}TDD Strategies
Fake it ('til you make it)
@Test
void getSquareArea_GivenEdgeLength_ReturnsArea() {
assertEquals(9, getSquareArea(3));
}// Iteration 1: Fake the result
int getSquareArea(int edge) { return 9; }
// Iteration 2: Make it look like a calculation
int getSquareArea(int edge) { return 3 * 3; }
// Iteration 3: Implement the real logic
int getSquareArea(int edge) {
return edge * edge;
}TDD Strategies
Triangulation
@Test
void getSquareArea_GivenVariousEdges_ReturnsArea() {
assertEquals(0, getSquareArea(0));
}
int getSquareArea(int edge) {
return 0;
}
@Test
void getSquareArea_GivenVariousEdges_ReturnsArea() {
assertEquals(0, getSquareArea(0));
assertEquals(1, getSquareArea(1));
}
int getSquareArea(int edge) {
return edge;
}@Test
void getSquareArea_GivenVariousEdges_ReturnsArea() {
assertEquals(0, getSquareArea(0));
assertEquals(1, getSquareArea(1));
assertEquals(4, getSquareArea(2));
}
int getSquareArea(int edge) {
return edge * edge;
}SESSION #3
Constraints:
-
TDD Ping-Pong
-
Behavior in test names
-
Inifinite table
-
Baby Steps
SESSION #4
All About Clean Code
Make your code easy to understand!
All About Clean Code
Make your code easy to understand!
// BAD
int d; // elapsed time in days
public List<int[]> getThem() { ... }
// BETTER
int elapsedTimeInDays;
public List<int[]> getFlaggedCells() { ... }Use descriptive, unambigious names
All About Clean Code
Make your code easy to understand!
// BAD
double circumference = radius * 2 * 3.14;
if (age >= 18) { ... }
// BETTER
static final double PI = 3.14;
static final int LEGAL_VOTING_AGE = 18;
double circumference = radius * 2 * PI;
if (age >= LEGAL_VOTING_AGE) { ... }Avoid Magic Numbers and Strings
All About Clean Code
Make your code easy to understand!
// BAD
public void registerUser(User user) {
if (user.getName() == null) throw new Exception();
database.save(user);
emailService.sendWelcome(user.getEmail());
}
// BETTER
public void registerUser(User user) {
validateUser(user);
userRepository.save(user);
emailService.sendWelcomeEmail(user);
}Functions are Small & Should Do One Thing
All About Clean Code
Make your code easy to understand!
// BAD
public void createMenu(
String title,
String body,
String buttonText,
boolean cancellable) {
// ...
}
// BETTER
public void createMenu(MenuConfig config) {
// ...
}Limit Function Arguments
All About Clean Code
Make your code easy to understand!
// BAD
// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) { ... }
// BETTER
if (employee.isEligibleForFullBenefits()) { ... }Don't use comments to explain complex code
SESSION #4
Constraints:
-
First 5 minutes planning!
-
Follow clean code rules, eg. small functions (max 3 lines)
-
No primitives (avoid primitive obsession)
-
Bonus: No loop (for/while)
SESSION #5
SESSION #5
Constraints:
-
SESSION #6
Webstar CodeRetreat 2025.11.27
By drawain
Webstar CodeRetreat 2025.11.27
- 77