- Write a failing test.
- Write the minimal amount of code for the test to pass.
- Refactor (if needed).
- Tests give confidence when refactoring since they act as a safety harness.
- Brittle tests are quickly found in the refactoring step of the TDD loop.
- Following the TDD loop results in an emergent design, which is often preferred over a plan-then-implement design.
Improves the readability of test cases, especially when there are many.
❌
def test_dog_makes_woof_sound():
assertTrue("woof" in Dog().bark().sounds())
✅
def test_dog_makes_woof_sound():
# Given
dog = Dog()
# When
dog.bark()
# Then
assertTrue("woof" in dog.sounds())
Allows you to test behaviours independently of one another.
Makes debugging easier when test cases are failing.
❌
def test_dog_makes_sounds():
# Given
dog = Dog()
# When
dog.bark()
dog.sniff()
# Then
assertTrue("woof" in dog.sounds())
assertTrue("sniff" in dog.sounds())
✅
def test_dog_makes_woof_sound():
# Given
dog = Dog()
# When
dog.bark()
# Then
assertTrue("woof" in dog.sounds())
def test_dog_makes_sniff_sound():
# Given
dog = Dog()
# When
dog.sniff()
# Then
assertTrue("sniff" in dog.sounds())
Independent tests are easier to reason about.
❌
dog = Dog()
def test_dog_makes_woof_sound():
# When
dog.bark()
# Then
assertEqual(dog.sounds(), ["woof"])
def test_dog_makes_sniff_sound():
# When
dog.sniff()
# Then
assertEqual(dog.sounds(), ["sniff"]) # Failure: expected ["sniff"] but got ["woof", "sniff"]
✅
def test_dog_makes_woof_sound():
# Given
dog = Dog()
# When
dog.bark()
# Then
assertEqual(dog.sounds(), ["woof"])
def test_dog_makes_sniff_sound():
# Given
dog = Dog()
# When
dog.sniff()
# Then
assertEqual(dog.sounds(), ["sniff"])