Expert · Algorithms, data structures and testing · topic 44 of 50 · 30 min
Testing your code
Explanation
Why test at all?
Checking your program by hand once is not enough. Every time you change the code, something that worked yesterday may break. A bug that comes back after it was fixed, or a feature that breaks because of a change somewhere else, is called a regression. Automatic tests re-check everything in a second, every time.
assert with a message
assert condition, "message" does nothing when the condition is True. When it is False, Python raises an AssertionError with your message. A good message says what was expected, so the failure explains itself.
def add_gst(price):
return round(price * 1.18, 2)
assert add_gst(100) == 118, "100 + 18% GST should be 118"
assert add_gst(0) == 0, "no price, no tax"
try:
assert add_gst(50) == 60, "50 + 18% GST should be 59"
except AssertionError as err:
print("Test failed:", err)Test functions
Group checks into functions whose names start with test_. Each test checks one behaviour. A common shape is Arrange, Act, Assert: prepare the input, call the code, check the result.
def apply_discount(price, percent):
if not 0 <= percent <= 100:
raise ValueError("percent must be 0 to 100")
return price - price * percent / 100
def test_normal_discount():
assert apply_discount(200, 10) == 180
def test_zero_discount():
assert apply_discount(200, 0) == 200
def test_bad_percent():
try:
apply_discount(200, 150)
except ValueError:
return # expected: test passes
assert False, "150% should raise ValueError"
for test in [test_normal_discount, test_zero_discount, test_bad_percent]:
test()
print("all tests passed")The unittest module
unittest comes with Python. You write a class that inherits from unittest.TestCase. Every method whose name starts with test is one test. Instead of assert you use helper methods such as assertEqual(a, b) and assertRaises(ErrorType). A method called setUp runs before every test, so each test gets fresh data.
import unittest
def cart_total(items):
return sum(price * qty for price, qty in items)
class TestCartTotal(unittest.TestCase):
def setUp(self):
self.items = [(40, 2), (15, 4)] # fresh for every test
def test_total(self):
self.assertEqual(cart_total(self.items), 140)
def test_empty_cart(self):
self.assertEqual(cart_total([]), 0)
def test_bad_item(self):
with self.assertRaises(TypeError):
cart_total([("forty", None)])
unittest.main(argv=[""], exit=False)pytest: the tool most teams use
pytest is a free third-party tool. It finds files named test_*.py, runs every function whose name starts with test_, and lets you use plain assert. When an assert fails, it shows the actual and expected values clearly. It also runs unittest classes, so you can mix both styles.
pip install pytest
pytest # run every test in the project
pytest -q # quieter output
pytest -k gst # only tests whose names contain "gst"
# Sample output:
# ..F
# FAILED test_billing.py::test_gst_on_50 - assert 59.0 == 60An edge case is an unusual input at the border of what your code accepts. Most bugs hide there, so test them on purpose.
- Empty input: an empty list, an empty string, no items in the cart.
- Zero and negative numbers: 0 rupees, -5 quantity.
- Boundaries: exactly the limit (marks of 35 when 35 is the pass mark), one below, one above.
- One item, many items, duplicates.
- Wrong type or bad format: "abc" where a number is expected.
| unittest | pytest | |
|---|---|---|
| Comes with Python | Yes | No, install with pip |
| How you check | self.assertEqual, self.assertRaises | Plain assert, pytest.raises |
| Test shape | Methods in a TestCase class | Plain functions (classes optional) |
| Setup before tests | setUp method | Fixtures (functions that provide data) |
Watch the lesson
Testing your code · 1/7
In this lesson you will learn to write tests: small programs that check your program.
Prefer a video by a teacher? Find free videos on “python unit testing unittest pytest tutorial for beginners” on YouTube ↗
In real life: Testing the fare calculator of a bus booking app
A bus booking app gives seniors 40% off and children under 5 a free ticket. One day a developer changes the discount code and children start paying full fare. With tests for each rule, including the edge case of a child exactly 5 years old, the mistake shows up on the developer's own laptop in seconds, long before any parent is charged wrongly.
def fare(base, age):
if age < 5:
return 0
if age >= 60:
return round(base * 0.6, 2)
return base
def test_fares():
assert fare(500, 3) == 0, "children under 5 travel free"
assert fare(500, 5) == 500, "a 5-year-old pays full fare (edge case)"
assert fare(500, 60) == 300, "seniors from 60 get 40% off"
assert fare(500, 30) == 500, "adults pay the base fare"
test_fares()
print("fare tests passed")Practice (0/3 done)
add_gst(price) is given. Write a test function test_add_gst() that uses assert to check at least two things: add_gst(100) == 118 and add_gst(0) == 0. The checker runs your test on the correct add_gst (it must pass) and on broken versions (it must fail).
order_total(items) is given: it adds price * qty for each (price, qty) pair and raises ValueError if any qty is negative. Complete the unittest class TestOrderTotal so it has at least 3 tests: a normal order, an empty order (total 0) and a negative quantity (use self.assertRaises(ValueError)). Your tests must pass on the correct code and catch broken versions.
On your computer, install pytest (pip install pytest). Create billing.py with a function and test_billing.py with at least four test functions, including two edge cases. Run pytest, then break the function on purpose and watch a test fail.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.