Series: Software Engineering Fundamentals | Part Part 14 of 19 > Delivered at Universidade Potiguar (UnP) in 2010
In this session, we explored Test-Driven Development (TDD) beyond the usual catchphrases. We focused on its impact on software quality, code evolution, and how testing is not a phase—but a development mindset. This was not a class to teach syntax; it was about how to think like a developer who questions assumptions and encodes confidence.
TDD is not about writing tests. It’s about designing with purpose.
Getting to the Root: Correct Problem, Correct Solution
We began by analyzing a classic trap in software projects: building beautiful solutions for the wrong problem. TDD interrupts this by forcing you to define expected behavior before writing logic. This pushes clarity before complexity.
Students were asked to refactor an existing method only after writing failing tests. The goal was to make them rethink design through tests instead of the other way around.
Here’s an example test we worked through:
@Test
public void testCalculateTotalWithDiscount() {
Cart cart = new Cart();
cart.add(new Product("Book", 50));
cart.applyDiscount("STUDENT10");
assertEquals(45, cart.getTotal());
}
Before even coding applyDiscount
, students needed to decide:
- What does a discount look like?
- Who qualifies for it?
- What’s the correct final state?
TDD’s Cycle: Red-Green-Refactor
We then studied the cycle that anchors TDD: Red-Green-Refactor. Students practiced each step with precision:
- Red: Write a failing test.
- Green: Write the minimal code to make it pass.
- Refactor: Improve code without changing behavior.
An early exercise involved string validation:
def test_should_not_allow_empty_username():
with pytest.raises(ValueError):
User(username="")
# Start with red, implement just enough to pass:
class User:
def __init__(self, username):
if username == "":
raise ValueError("Username required")
Refactoring came later: extracting validation, adding constraints, removing duplication.
Activities That Reinforce the Loop
I introduced a classroom kata: a pricing system for ticket types (student, regular, senior). Each student wrote a single failing test, passed it, and refactored—then passed it to the next group member to repeat the cycle.
This created code that was born and evolved through deliberate constraints, not overengineering. At the end of 30 minutes, we had a pricing engine that handled four rules, had no duplication, and was fully covered by tests.
Another session exercise revolved around command-line calculator logic:
describe Calculator do
it "adds two numbers" do
calc = Calculator.new
expect(calc.add(2, 3)).to eq(5)
end
end
Even simple examples teach how to clarify behavior through tests, not by guessing at future needs.
Acceptance TDD: Ensuring We Build the Right Thing
While TDD focuses on design, we also covered Acceptance TDD (ATDD)—validating that what we build aligns with stakeholder expectations. Students wrote acceptance tests for a search feature with constraints like “must return no more than 10 results in under 1 second.”
They quickly saw that the test itself became a requirement artifact, useful for alignment between devs, QA, and users.
We reviewed what happens when no such alignment exists: features built with misunderstood logic, poorly tested under edge conditions, or lacking validation for the true need.
Facilitators and team leads can use these techniques during backlog refinement. If you can’t express the expected result as an executable test, you may not understand the need yet.
Final Thoughts
My goal with this class wasn’t just to teach TDD as a methodology—it was to make students internalize it as a feedback and design strategy.
TDD helps developers stay grounded: no big jumps, no wasted code. It’s a loop of discovery and simplification. And in a world where every change introduces risk, test-first design is your guardrail.
---Posted as part of the Software Engineering course journal. Today we learned that TDD isn’t just about testing—it’s about designing with confidence and building systems that evolve through deliberate, validated steps.
Series Navigation
- Introduction: Part 1 - Why Software Engineering?
- Previous: Part 13 - Software Testing
- Next: Part 15 - Unit Testing with JUnit
- Current: Part 14 - Test-Driven Development
- Complete series: Why Software Engineering? | Taming Complexity | Waterfall Model | Evolutionary Models | Agile Mindset | Scrum Productivity | Scrum Cycle | XP Quality & Courage | XP Principles & Practices | XP in Practice | Domain-Driven Design | Requirements & Testing | Software Testing | Test-Driven Development | Unit Testing with JUnit