Introduction
A unit test verifies the smallest testable piece of a program — typically a single function or method — in complete isolation from the rest of the system. Because unit tests don't touch databases, networks, or the filesystem, they run in milliseconds and can be executed thousands of times a day, making them the foundation of a fast feedback loop.
Cricket analogy: A bowling machine testing a batter's reaction to just one specific delivery type, isolated from any real match pressure, is the smallest testable unit of skill and can be repeated hundreds of times in a single net session.
Explanation
Most unit tests follow the AAA pattern: Arrange, Act, Assert. In the Arrange step, you set up the inputs and any objects the code under test needs. In the Act step, you call the function or method being tested exactly once. In the Assert step, you check that the actual result matches the expected result. Keeping these three steps visually separated makes tests easy to read and easy to debug when they fail, because it is immediately clear what was set up, what was executed, and what was checked.
Cricket analogy: Arrange is setting the field and choosing the delivery to bowl, Act is actually bowling that one ball, and Assert is checking whether the umpire signals wicket or runs — keeping these separate makes it obvious what was set up versus what happened.
Good unit tests target pure functions where possible — functions whose output depends only on their inputs, with no hidden state or side effects. Pure functions are the easiest code to unit test because there is nothing to isolate: no mocking of collaborators is required, and the same input always produces the same output.
Cricket analogy: A batting drill against a bowling machine set to a fixed line and length is the easiest thing to practice because there's no unpredictable field placement or crowd noise to account for — the same input always produces the same shot outcome.
Example
# A pure function under test
def calculate_total(price: float, quantity: int, tax_rate: float = 0.0) -> float:
if quantity < 0:
raise ValueError("quantity cannot be negative")
subtotal = price * quantity
return round(subtotal * (1 + tax_rate), 2)
# pytest-style unit tests using the AAA pattern
def test_calculate_total_without_tax():
# Arrange
price = 10.0
quantity = 3
# Act
result = calculate_total(price, quantity)
# Assert
assert result == 30.0
def test_calculate_total_with_tax():
# Arrange
price = 20.0
quantity = 2
tax_rate = 0.05
# Act
result = calculate_total(price, quantity, tax_rate)
# Assert
assert result == 42.0
def test_calculate_total_raises_on_negative_quantity():
# Arrange
price = 10.0
quantity = -1
# Act / Assert
import pytest
with pytest.raises(ValueError):
calculate_total(price, quantity)Analysis
Each test above arranges its inputs explicitly, calls calculate_total exactly once, and makes a single, clear assertion (or checks for a raised exception). Because calculate_total is a pure function with no dependency on a database, clock, or external service, the tests need no setup beyond simple local variables and run in microseconds. This isolation is exactly what makes unit tests valuable: when test_calculate_total_with_tax fails, the developer knows the bug is inside calculate_total itself, not in some unrelated part of the system, because nothing else was involved in the test.
Cricket analogy: Each net session drills one specific delivery type with a clearly stated intent, faces exactly one ball per rep, and checks one clear outcome, so when a batter mistimes a shot the coach knows the flaw is in that specific technique, not the whole game.
Key Takeaways
- A unit test verifies a single function or method in isolation from the rest of the system.
- The AAA pattern (Arrange, Act, Assert) keeps tests readable and easy to debug.
- Pure functions are the easiest and most valuable targets for unit tests.
- Unit tests should run fast (milliseconds) since they form the bulk of the test suite.
- A failing unit test should point precisely to the broken unit, not to the wider system.
Practice what you learned
1. What does the 'Act' step of the AAA pattern do?
2. Why are pure functions easy to unit test?
3. In pytest, which construct is used to assert that a function raises a specific exception?
4. What is a defining characteristic of a good unit test?
Was this page helpful?
You May Also Like
Software Testing Fundamentals
An overview of why we test software, the testing pyramid, and the difference between black-box and white-box testing.
Integration and System Testing
Understand how integration testing checks components working together and system testing validates the whole application end-to-end.
Mocking and Test Doubles
Learn the five canonical test double types—dummy, stub, spy, mock, and fake—and how to use unittest.mock in Python.
Test-Driven Development
Learn the Red-Green-Refactor cycle that drives code design by writing tests before implementation.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesMicroservices Study Notes
Software Architecture · 30 topics
Software EngineeringTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringDesign Patterns Study Notes
Software Design · 30 topics
Software EngineeringGit & Version Control Study Notes
Bash · 40 topics
Software EngineeringSystem Design Study Notes
Architecture · 40 topics