- Apply advanced testing patterns like fixtures and parametrization
- Use mocking effectively for complex dependencies
- Test asynchronous code and exceptions properly
- Implement integration testing and measure test coverage
Advanced Testing Techniques
Beyond Basic Unit Tests
Once you master basic unit testing, there are advanced techniques that can make your tests more powerful and maintainable.
Fixtures and Setup
pytest Fixtures
import pytest
@pytest.fixture
def sample_data():
return {'name': 'Alice', 'age': 30}
def test_user_creation(sample_data):
user = User(**sample_data)
assert user.name == 'Alice'
unittest setUp and tearDown
class TestDatabase(unittest.TestCase):
def setUp(self):
self.db = create_test_database()
def tearDown(self):
self.db.cleanup()
Parameterized Tests
pytest Parametrize
@pytest.mark.parametrize("input,expected", [
(2, 4),
(3, 9),
(4, 16),
])
def test_square(input, expected):
assert square(input) == expected
unittest Subtests
class TestMath(unittest.TestCase):
def test_square(self):
for input, expected in [(2, 4), (3, 9), (4, 16)]:
with self.subTest(input=input):
self.assertEqual(square(input), expected)
Mocking Complex Objects
Advanced Mocking
from unittest.mock import MagicMock
def test_api_call():
mock_response = MagicMock()
mock_response.json.return_value = {'status': 'success'}
with patch('requests.get', return_value=mock_response):
result = api_call()
assert result['status'] == 'success'
Mock Side Effects
mock_obj.side_effect = [1, 2, ValueError('error')]
# First call returns 1, second returns 2, third raises ValueError
Testing Exceptions
Expected Exceptions
def test_invalid_input():
with pytest.raises(ValueError, match="Invalid input"):
process_data("invalid")
unittest Context Manager
def test_division_by_zero(self):
with self.assertRaises(ZeroDivisionError):
divide(10, 0)
Testing Asynchronous Code
pytest Async
@pytest.mark.asyncio
async def test_async_function():
result = await async_operation()
assert result == 'expected'
unittest Async
class TestAsync(unittest.TestCase):
async def test_async_operation(self):
result = await async_operation()
self.assertEqual(result, 'expected')
Property-Based Testing
Hypothesis Library
from hypothesis import given, strategies as st
@given(st.integers(), st.integers())
def test_add_commutative(a, b):
assert add(a, b) == add(b, a)
Test Coverage
Measuring Coverage
pytest --cov=myapp --cov-report=html
Coverage Goals
Aim for high coverage but focus on meaningful tests rather than 100% coverage.
Integration Testing
Testing with Real Dependencies
def test_full_user_workflow():
# Test complete user registration and login flow
user_id = register_user('test@example.com', 'password')
token = login_user('test@example.com', 'password')
assert token is not None
Test Organization
Test Discovery
test_*.pyfiles*_test.pyfiles- Classes named
Test* - Methods named
test_*
Test Categories
- Unit tests
- Integration tests
- End-to-end tests
- Performance tests
Continuous Integration
Automated Testing
Run tests automatically on code changes using CI/CD pipelines.
Pre-commit Hooks
Run tests before committing code.
Best Practices
Test Readability
Tests should be easy to read and understand.
Test Maintainability
Keep tests DRY (Don't Repeat Yourself).
Test Reliability
Avoid flaky tests that sometimes pass and sometimes fail.
Test Speed
Keep the test suite fast to encourage frequent running.
Advanced testing techniques help you write more robust, maintainable code. Combine these techniques with good testing practices to build high-quality software that you can confidently modify and extend.
