Python Testing & Debugging
Write reliable code and squash bugs like a pro!
1. Testing with pytest
pytest is a popular, concise testing framework.
Installation
pip install pytest
Example Test
# test_math.py
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0
def test_add_edge_cases():
with pytest.raises(TypeError):
add("2", 3) # Should raise TypeError
Run Tests:
pytest test_math.py -v # -v for verbose output
Key Features:
- Auto-discovers tests (files named
test_*.py
or*_test.py
). - Fixtures for reusable setup/teardown.
- Rich plugin ecosystem (e.g.,
pytest-cov
for coverage).
2. Testing with unittest
unittest is Python’s built-in testing framework (inspired by JUnit).
Example Test
# test_strings.py
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual("hello".upper(), "HELLO")
def test_split(self):
s = "hello world"
self.assertEqual(s.split(), ["hello", "world"])
with self.assertRaises(TypeError):
s.split(2) # Invalid argument
if __name__ == "__main__":
unittest.main()
Run Tests:
python -m unittest test_strings.py -v
Key Methods:
Method | Purpose |
---|---|
assertEqual(a, b) |
Check if a == b |
assertTrue(x) |
Check if x is True |
assertRaises(Error) |
Check if code raises an error |
3. Debugging with pdb
pdb (Python Debugger) helps you trace code execution and inspect variables.
Basic Commands
Command | Action |
---|---|
n |
Execute next line |
c |
Continue until next breakpoint |
s |
Step into function |
l |
List source code around current line |
p <var> |
Print variable value |
q |
Quit debugger |
Example Workflow
# debug_example.py
def calculate_sum(numbers):
total = 0
for num in numbers:
import pdb; pdb.set_trace() # Set breakpoint
total += num
return total
print(calculate_sum([10, "20", 30])) # Intentional bug
Debugging Session:
> debug_example.py(4)calculate_sum()
-> total += num
(Pdb) p num # Print current num → "20"
(Pdb) p type(num) # <class 'str'> → TypeError!
(Pdb) q # Exit and fix the code
4. Real-World Scenarios
Scenario 1: Testing a Flask API
# test_app.py (using pytest)
from my_flask_app import app
import pytest
@pytest.fixture
def client():
app.config["TESTING"] = True
return app.test_client()
def test_home_page(client):
response = client.get("/")
assert response.status_code == 200
assert b"Welcome" in response.data
def test_invalid_route(client):
response = client.get("/invalid")
assert response.status_code == 404
Scenario 2: Debugging a Data Pipeline
def process_data(data):
result = []
for item in data:
# Debugging a data transformation issue
import pdb; pdb.set_trace()
processed = item * 2 # Example bug: item might be a string
result.append(processed)
return result
process_data([5, "10", 15])
5. Best Practices
Testing:
- Follow AAA Pattern (Arrange, Act, Assert).
- Write small, focused tests.
- Use parameterized tests for multiple inputs:
@pytest.mark.parametrize("a,b,expected", [(2,3,5), (-1,1,0)])
def test_add(a, b, expected):
assert add(a, b) == expected
Debugging:
- Use conditional breakpoints:
if condition:
import pdb; pdb.set_trace()
- Debug tests with
pytest --pdb
(drop into debugger on failure).
Common Mistakes
- ❌ Testing implementation, not behavior: Tests should focus on what the code does, not how.
- ❌ Overusing print statements: Use pdb for systematic debugging.
- ❌ Ignoring edge cases: Test empty inputs, invalid types, and boundary values.
Key Takeaways
- ✅ pytest: Concise syntax, fixtures, and plugins.
- ✅ unittest: Built-in, class-based structure.
- ✅ pdb: Step through code, inspect variables, and fix bugs.
Practice Problem
Write a pytest test for a function that divides two numbers.
Add error handling to catch division by zero and test it.
Debug a failing test using pdb.
What’s Next?
- Learn test coverage with
pytest-cov
or explore debugging in IDEs (VSCode, PyCharm)!
Tags:
python