Building Better Software with Test-Driven Development: Comparing the Chicago and London Approaches

Wissam Noureddine
6 min readMar 16, 2023
Photo by Lachlan Gowen on Unsplash

Let's first remember what is TDD before divings into the comparison. Test-Driven Development (TDD) is a software development approach that emphasizes writing automated tests before writing any actual code. The TDD process typically involves the following steps:

  1. Write a test: The first step is to write a test that specifies the desired behavior of the code. The test should be as simple as possible and should fail at first.
  2. Run the test: Once the test is written, the developer runs the test to ensure that it fails, indicating that the desired behavior is not yet implemented.
  3. Write the code: The developer then writes the code necessary to make the test pass. The code should be as simple as possible and should only implement the behavior necessary to pass the test.
  4. Run the tests: Once the code is written, the developer runs all of the tests to ensure that the new code didn’t break any existing functionality.
  5. Refactor the code: Finally, the developer refactors the code to improve its quality and maintainability. This step is optional but can help ensure that the codebase remains easy to maintain and extend over time.

A Simple Example

Suppose we want to create a simple calculator that can add two numbers. Here’s how we might use the TDD approach to implement this functionality:

  1. Write the failing test:
public class CalculatorTest {
@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
}

2. Implement the simplest possible code that could make the test pass:

public class Calculator {
public int add(int a, int b) {
return a + b;
}
}

3. Run the test to ensure it passes

4. Refactor the code if necessary to improve its design

5. Repeat the process for the next test:

public class CalculatorTest {
@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
@Test
public void testAdditionWithNegativeNumbers() {
Calculator calculator = new Calculator();
int result = calculator.add(-2, -3);
assertEquals(-5, result);
}
}

While TDD can be a powerful tool for software development, it can also be challenging to implement correctly. It requires a significant shift in mindset and often takes time to master. However, for those who are willing to put in the effort, TDD can lead to more efficient and effective software development.

“Test-driven development is not a testing methodology. It is a design methodology, and it forces you to think through your requirements one at a time, and every time you think through one requirement, you have to think about what are the simple, understandable steps that will satisfy that requirement, and you have to express those in code. And by doing that, you’re led to a better design.” — Kent Beck, the creator of Test-Driven Development

Two popular schools, Chicago and London, have their unique approaches to TDD. In this article, we’ll explore the differences between the two and how they can benefit your software development projects.

Chicago TDD Approach

The Chicago School of TDD is an approach that assert depending on the return values (An example of this approach can be found in the previous test example). It starts from the inside of the application, usually the domain, and works out towards the APIs. This approach has both advantages and disadvantages.

One of the advantages of the Chicago School approach is that it tends to produce tests that are decoupled from the implementation. This decoupling means that developers can experiment with changing the software without fear of breaking it. In this way, the approach provides a strong safety net for continuous refactoring, a vital part of the software development process.

Another advantage of the Chicago School approach is that it promotes high cohesion in the codebase. As the tests become more general, the production code becomes more specific, and this promotes loose coupling, high code quality, and testability. With high cohesion, the codebase is easier to maintain and extend, ensuring that the application remains robust over time.

London TDD Approach

This approach originated from the book “Growing Object-Oriented Software, Guided by Tests” by Steve Freeman and Nat Pryce — This book focuses on the London approach to TDD and provides practical guidance on how to apply it in real-world scenarios. In this approach, developers begin testing from the outside of an application, typically starting with the APIs or controllers. This means they write tests for the behavior of the application from the perspective of the user or client, to ensure that the external interface is working as intended.

Once the external behavior of the application is confirmed to be correct, the developers then move inwards towards the lower layers of the application. This includes testing domain models, which are the business logic layer of the application, and eventually testing down to the persistence layer, which handles the storage and retrieval of data.

The London TDD approach comes with its own set of pros and cons. One of the advantages of this approach is that it is behavior-focused. In this approach, developers write tests for abstractions that don’t exist yet, allowing them to create a high level of logic first. This approach might require a lot of test doubles (dummies, stubs, spies, and mocks), but it ensures that developers don’t write dead code. By writing tests for the high-level logic, developers can ensure that their application is working as expected from the outside in.

Another advantage of the London TDD approach is that it emphasizes Command-Query separation. This means that the approach clearly separates commands (actions) from queries (values). This discipline helps manage side effects and makes the application easier to understand and maintain.

Below is an example utilizing Mockito a Java mocking library to test the Calculator class applying the London TDD approach.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

public class CalculatorTest {

@Test
public void testAddition() {
CalculatorService mockCalculatorService = mock(CalculatorService.class);
when(mockCalculatorService.add(3, 4)).thenReturn(7);
Calculator calculator = new Calculator(mockCalculatorService);

int result = calculator.add(3, 4);

assertEquals(7, result);
verify(mockCalculatorService, times(1)).add(3, 4);
}
}

Which Approach Should You Use?

When deciding which approach to use for TDD, it is important to consider the complexity of the component. If the software component involves business requirements, such as ordering and adding items to a cart, the Chicago-style TDD approach is ideal. This approach helps developers to focus on the final output and enables them to get to market faster, as well as refactor the code as they go.

However, if the component being tests acts a facade or orchestrator, such as component connecting two systems, the London-style TDD approach is more suitable. This approach involves splitting the problem into multiple stages and verifying each stage of the algorithm separately by utilizing test doubles, which requires a higher level of granularity. By using the London-style approach, developers can ensure that each stage of the algorithm is functioning correctly, resulting in a robust and reliable solution.

In my perspective, it’s advisable to steer clear of adopting the London approach if the option to implement the Chicago approach exists. Chances are very high that you might find yourself utilizing both methodologies within your project.

Approaches to look into

  1. Acceptance Test-Driven Development (ATDD): ATDD is a collaborative approach that involves the whole team, including developers, testers, and business stakeholders. It focuses on creating acceptance tests that verify the requirements and functionality of the software.
  2. Behavior-Driven Development (BDD): BDD is an extension of TDD that emphasizes collaboration between developers, testers, and business stakeholders to define and verify the behavior of the software. It uses a structured language to define the behavior in terms of scenarios that can be understood by all stakeholders.
  3. Agile Testing Quadrants: This approach categorizes tests into four quadrants based on their purpose and level of automation. It helps teams to balance the focus between functional and non-functional testing, as well as between manual and automated testing.

Further Readings 😀

Here are some additional resources you may find helpful for learning more about the Chicago and London approaches to TDD:

  1. “Test-Driven Development: By Example” by Kent Beck — This book provides a thorough introduction to TDD and includes examples of both the Chicago and London approaches.
  2. “The Art of Agile Development” by James Shore and Shane Warden — This book includes a chapter on TDD that covers both the Chicago and London approaches.
  3. “Clean Code: A Handbook of Agile Software Craftsmanship” by Robert C. Martin — This book covers various aspects of writing clean and maintainable code, including TDD, and provides examples of both the Chicago and London approaches.
  4. “Introduction to TDD and the Chicago and London Schools” by Dave Farley — This video provides an overview of TDD and compares the Chicago and London approaches.

--

--

Wissam Noureddine

Interested in crafting efficient solutions for complex problems with my proficiency in programming languages, methodologies, and design principles 😀