Test-Driven Development (TDD) is the practice of working in a structured cycle where writing tests comes before writing production code. The process involves three steps, sometimes called the red-green-refactor cycle:
- Write a failing test
- Make the test pass by writing just enough production code
- Refactor the production code to meet your quality standards
Research shows TDD has several benefits: it improves test coverage, reduces the number of bugs, increases confidence, and facilitates code reuse. This practice also helps reduce distractions and keep you in the flow. TDD also has its limitations and is not a silver bullet! See the Wikipedia article about TDD for a detailed explanation and references.
Here is a short practical example. Assume you need to modify the following voting algorithm to support the option for voters to abstain:
def outcome(ballots): if ballots.count(Vote.FOR) > len(ballots) / 2: return "Approved" return "Rejected" |
1. We start by writing a failing test - as expected, the test doesn't even compile:
def test_abstain_doesnt_count(self): self.assertEqual(outcome([Vote.FOR, Vote.FOR, Vote.AGAINST, Vote.ABSTAIN]), "Approved") |
2. We fix the compilation error by including the missing enum option:
class Vote(Enum): FOR = 1 AGAINST = 2 ABSTAIN = 3 |
Now that the test compiles, we fix the production code to get all tests passing:
def outcome(ballots): if ballots.count(Vote.FOR) > (len(ballots) - ballots.count(Vote.ABSTAIN)) / 2: return "Approved" return "Rejected" |
3. We now refactor the code to improve clarity, and complete an iteration of the TDD cycle:
def outcome(ballots): counts = collections.Counter(ballots) return "Approved" if counts[Vote.FOR] > counts[Vote.AGAINST] else "Rejected" |
Learn more about TDD in the book Test Driven Development: By Example, by Kent Beck.