🚀
Advanced
pytest · Fixtures · parametrize · Mock · Coverage
Week 6 — Testing with pytest
Write professional tests with pytest. Use fixtures for setup/teardown, parametrize for data-driven tests, mock external dependencies, and measure code coverage.
pytestfixtureparametrizemockcoverageTDD
Duration
⏱ 2.5 hours
Level
📊 Advanced
Prerequisite
🎯 Intermediate Week 5
OUTCOME
Write a full test suite for a BankAccount class with 90%+ coverage
What you'll learn
- 1Write test functions and classes with pytest
- 2Use fixtures for reusable setup and teardown
- 3Parametrize tests to cover multiple inputs
- 4Mock external dependencies with unittest.mock
- 5Measure and improve code coverage
1. pytest Basics
python
# test_calculator.py
import pytest
def add(a, b): return a + b
def divide(a, b):
if b == 0: raise ZeroDivisionError
return a / b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
# Run: pytest test_calculator.py -v2. Fixtures
python
import pytest
from bank import BankAccount
@pytest.fixture
def account():
"""Fresh account with $100 balance."""
acct = BankAccount("test")
acct.deposit(100)
return acct
def test_deposit(account):
account.deposit(50)
assert account.balance == 150
def test_withdraw(account):
account.withdraw(30)
assert account.balance == 70
def test_overdraft(account):
with pytest.raises(ValueError, match="Insufficient"):
account.withdraw(200)3. Parametrize & Mock
python
import pytest
from unittest.mock import patch, MagicMock
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5), (-1, 1, 0), (0, 0, 0), (100, -50, 50)
])
def test_add_param(a, b, expected):
assert add(a, b) == expected
def test_with_mock():
with patch("requests.get") as mock_get:
mock_get.return_value = MagicMock(status_code=200, json=lambda: {"ok": True})
# code that calls requests.get will use the mock
resp = requests.get("http://example.com")
assert resp.status_code == 200💻 Examples
Run these examples and check the output yourself.
test_bank.py— Full BankAccount test suite
CODE
import pytest
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
@property
def balance(self): return self._balance
def deposit(self, amount):
if amount <= 0: raise ValueError("Must be positive")
self._balance += amount
def withdraw(self, amount):
if amount > self._balance: raise ValueError("Insufficient funds")
self._balance -= amount
@pytest.fixture
def empty_acct(): return BankAccount("Test")
@pytest.fixture
def funded_acct(): a = BankAccount("Test"); a.deposit(1000); return a
def test_initial_balance(empty_acct):
assert empty_acct.balance == 0
@pytest.mark.parametrize("amount", [1, 100, 99999])
def test_deposit_valid(empty_acct, amount):
empty_acct.deposit(amount)
assert empty_acct.balance == amount
def test_deposit_invalid(empty_acct):
with pytest.raises(ValueError):
empty_acct.deposit(-10)
def test_withdraw_valid(funded_acct):
funded_acct.withdraw(300)
assert funded_acct.balance == 700
def test_withdraw_overdraft(funded_acct):
with pytest.raises(ValueError, match="Insufficient"):
funded_acct.withdraw(5000)
📝 Exercises
Try them yourself first, then open the solution to compare.
Exercise 1
TDD: Stack implementation
Goal: Write tests first, then implement a Stack class to make them pass.
Requirements
- Write test_stack.py with 8+ test cases
- Cover: push, pop, peek, is_empty, overflow (max_size)
- Achieve 100% coverage: pytest --cov=stack test_stack.py
▶Toggle solution
SOLUTION
# test_stack.py
import pytest
from stack import Stack, StackOverflowError
@pytest.fixture
def s(): return Stack(max_size=3)
def test_empty(s): assert s.is_empty()
def test_push_pop(s): s.push(1); assert s.pop() == 1
def test_peek(s): s.push(99); assert s.peek() == 99; assert not s.is_empty()
def test_pop_empty(s):
with pytest.raises(IndexError): s.pop()
def test_overflow(s):
for i in range(3): s.push(i)
with pytest.raises(StackOverflowError): s.push(999)
# stack.py
class StackOverflowError(Exception): pass
class Stack:
def __init__(self, max_size=None):
self._data, self.max_size = [], max_size
def push(self, v):
if self.max_size and len(self._data) >= self.max_size:
raise StackOverflowError
self._data.append(v)
def pop(self):
if not self._data: raise IndexError
return self._data.pop()
def peek(self):
if not self._data: raise IndexError
return self._data[-1]
def is_empty(self): return not self._data
Example code / lecture materials
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗