Test-Driven Development
Test-Driven Development (TDD) & Code Quality
Test-Driven Development (TDD) is an established software engineering discipline where tests are written before the implementation code. Formulated by Kent Beck, TDD transforms automated tests from an afterthought into a design tool that enforces loose coupling, modularity, and high code quality.
1. The Red-Green-Refactor Cycle
The core methodology of TDD operates in a tight, iterative three-stage loop:
- 1RED: Write a failing test verifying a single business rule. Run the test and observe it fail.
- 2GREEN: Implement the simplest, most direct code to make the test pass (even hardcoding values if appropriate initially).
- 3REFACTOR: Restructure the code, remove duplication, and enhance readability while verifying that all tests continue to pass.
2. Practical TDD Walkthrough: Building a Token Bucket Rate Limiter
Let's build a production Token Bucket Rate Limiter using the TDD cycle.
Iteration 1: Rejecting requests when bucket is empty
- Step 1 (RED): We write a test expecting an empty bucket to reject an acquisition.
- Step 2 (GREEN): We write the minimal code to satisfy the test.
Result: Test passes! (GREEN)
Iteration 2: Refilling tokens over time
- Step 1 (RED): We write a test verifying that tokens refill as time elapses.
- Step 2 (GREEN): Implement timestamp-based token replenishment.
Result: Test passes! (GREEN)
- Step 3 (REFACTOR): Optimize math, add thread-safety with
threading.Lock, and add type annotations without breaking the existing test suite.
3. Code Coverage Metrics: pytest-cov
Code coverage measures the proportion of executable statements verified by automated tests. In Python, coverage is measured using the coverage package or pytest-cov:
Line Coverage vs Branch Coverage
- Line (Statement) Coverage: Measures whether a line of code was touched during execution.
- Branch Coverage: Verifies that both branches of every conditional statement (
if condition: ... else: ...) were evaluated. Branch coverage is vastly superior for identifying subtle logic flaws.
4. TDD Anti-Patterns to Avoid
| Anti-Pattern | Description | Remediation |
|---|---|---|
| Testing Implementation Details | Asserting private variables (_internal_cache) instead of public API behaviors. | Test public contracts; private mechanics should be free to refactor. |
| The 100% Coverage Illusion | Achieving high line coverage by running code without meaningful assertions. | Focus on edge cases, branch boundaries, and invariant validation. |
| Brittle Mocks | Mocking internal methods of the class under test. | Only mock external boundaries (network, disk, clock). |
| Writing Tests After Code | Writing code first and reverse-engineering tests to fit implementation bugs. | Stick strictly to the Red-Green-Refactor discipline. |
5. Architectural Summary Table
| Phase | Goal | Mindset |
|---|---|---|
| RED | Formulate a clear specification via a failing test | "What should the public interface and behavior be?" |
| GREEN | Make the test pass as rapidly as possible | "What is the simplest solution to verify correctness?" |
| REFACTOR | Clean up design and eliminate technical debt | "How can this be made cleaner while keeping tests green?" |
Multiple Choice Questions
1.
What are the three sequential phases of the Test-Driven Development (TDD) cycle? A. Plan, Code, Ship B. Red (write failing test), Green (make test pass), Refactor (clean code) C. Compile, Link, Execute D. Mock, Patch, Assert
2.
Why is Branch Coverage considered more rigorous than standard Line Coverage? A. Branch coverage requires less memory. B. Branch coverage verifies that both True and False pathways of every conditional branch are evaluated, whereas line coverage only checks if a line was executed. C. Branch coverage compiles tests to machine code. D. Line coverage cannot detect syntax errors.
if condition. Branch coverage ensures all logical decision paths (if, elif, else) are exercised.3.
What is a major testing anti-pattern when writing unit tests under TDD? A. Using fixtures to manage state. B. Testing private internal implementation details rather than observable public behaviors. C. Using descriptive test names. D. Running tests in continuous integration.
4.
What is the primary goal of the "GREEN" phase in TDD? A. To achieve perfect architectural perfection immediately. B. To write the minimal code necessary to satisfy the failing test and verify correctness as quickly as possible. C. To generate documentation for end users. D. To deploy code to production.
5.
Which command line flag in pytest-cov enables evaluation of conditional branch paths? A. --cov-branch B. --branch-mode C. --verify-all D. --deep-coverage
--cov-branch flag instructs coverage.py and pytest-cov to measure branch coverage across all conditional statements.Project: Tested Calculator Application
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
| Previous Lesson | Next Lesson |
|---|---|
| Mocking and Fixtures | Project: Tested Calculator Application |
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.