Testing Python with pytest
Write focused tests around observable behavior, use parametrization and fixtures appropriately, and structure code so tests do not depend on console or filesystem accidents.
Before this lesson
Write behavior-focused pytest tests
Parametrize boundary cases
Use fixtures without hiding the scenario
The short answer
A good test arranges meaningful input, invokes one behavior, and asserts an observable result. Test boundaries and failure contracts, keep tests deterministic, and use parametrization when the same rule must hold for several cases.
Build the mental model
pytest discovers test functions and reports assertion differences clearly. A test should fail for one understandable reason. Test names describe the rule, not implementation steps. Exceptions are behavior too and can be checked with pytest.raises.
The central idea in this lesson is regression protection. Read the rule, predict a concrete outcome, and then run the smallest example that can confirm or reject the prediction. This separates knowledge from familiarity with syntax.
Make the design choice explicit
Parametrize a small table of boundary inputs instead of copying a test body. Fixtures are useful for explicit reusable setup, but deep fixture chains obscure the scenario. tmp_path provides isolated filesystem space.
def shipping_cost(weight):
if weight <= 0:
raise ValueError("weight must be positive")
return 5.0 if weight <= 2 else 8.5
cases = [(0.5, 5.0), (2.0, 5.0), (2.1, 8.5)]
for weight, expected in cases:
assert shipping_cost(weight) == expected
print("3 boundary cases passed")Expected output
3 boundary cases passed
Trace the example before running it. Identify each input, transformation, returned value, and side effect. Then change one boundary value and explain why the new behavior follows from the rule rather than memorizing the output.
Recognize failure modes
Do not assert private implementation details, call real external services, depend on test order, or sleep to coordinate timing. A mock is a design tool for a boundary—not a way to make every collaborator imaginary.
Use a professional practice loop
Turn the concept into a repeatable workflow: write down the expected behavior, implement one coherent change, run a representative example, and retain a regression check. If the result surprises you, capture the exact input and error before changing anything.
Review the program for names, boundaries, and hidden side effects. A solution is complete when another developer can understand its contract, reproduce its setup, and verify both the successful path and one meaningful failure path.
Quick knowledge check
Answer before you reveal.
01What should a test primarily assert?
Observable behavior promised by the contract, rather than incidental internal implementation.
02What four steps make the practice loop reliable?
Predict the behavior, make one coherent change, run it, and retain a check that detects regression.
Exercise
Practice challenge
Create pytest tests for a discount function, including normal values, exact thresholds, invalid input, and a parametrized table.
Requirements
- The successful path produces the documented result
- At least one boundary or invalid case is handled deliberately
- Calculation or domain logic is separated from console interaction
Optional extension: Add one automated regression check for the most important rule.
Open in Python compilerLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
Is regression protection only important in large programs?
No. Small programs reveal the same rules with less noise, and learning the rule early prevents fragile habits from becoming architecture.
Should I memorize every API used here?
No. Memorize the mental model and how to verify behavior. Use documentation for exact names and parameters when needed.
How do I know the exercise is finished?
Meet every success criterion, test at least one boundary or failure case, and explain why the output follows from the code.