- Understand unit testing concepts and frameworks
- Write effective unit tests using unittest and pytest
- Apply testing best practices and patterns
- Use mocking and patching for isolated testing
Writing Unit Tests
What is Unit Testing?
Unit testing is like testing individual ingredients in a recipe before combining them. You isolate each function or method and test it independently to ensure it works correctly.
Python Testing Frameworks
unittest - Python's Built-in Framework
import unittest
class TestCalculator(unittest.TestCase):
def test_addition(self):
calc = Calculator()
result = calc.add(2, 3)
self.assertEqual(result, 5)
if __name__ == '__main__':
unittest.main()
pytest - A Popular Alternative
def test_addition():
calc = Calculator()
assert calc.add(2, 3) == 5
Writing Effective Unit Tests
Test Structure
Each test should follow the Arrange-Act-Assert pattern:
- Arrange: Set up test data and objects
- Act: Execute the code being tested
- Assert: Verify the expected outcome
Naming Conventions
Use descriptive names that explain what the test does:
def test_calculate_total_with_valid_input():
def test_calculate_total_with_empty_cart():
def test_calculate_total_with_negative_price():
Testing Different Scenarios
Test both happy paths and edge cases:
- Valid inputs
- Invalid inputs
- Boundary conditions
- Error conditions
Common Assertions
Equality Assertions
self.assertEqual(a, b) # unittest
assert a == b # pytest
Truthiness Assertions
self.assertTrue(x)
self.assertFalse(x)
self.assertIsNone(x)
Exception Assertions
with self.assertRaises(ValueError):
function_that_raises_error()
Mocking and Patching
When testing code that depends on external services or complex objects, use mocking:
from unittest.mock import Mock, patch
@patch('module.external_service')
def test_function_with_mock(self, mock_service):
mock_service.return_value = 'expected_result'
result = my_function()
self.assertEqual(result, 'expected_result')
Test Organization
Test Files
Place tests in a tests/ directory with names like test_module.py
Test Classes
Group related tests in classes:
class TestUserAuthentication(unittest.TestCase):
def test_valid_login(self):
# test code
def test_invalid_password(self):
# test code
Running Tests
With unittest
python -m unittest test_module.py
python -m unittest tests/
With pytest
pytest test_module.py
pytest tests/
Best Practices
Keep Tests Fast
Unit tests should run quickly to encourage frequent execution.
Isolate Tests
Each test should be independent and not rely on others.
Test Behavior, Not Implementation
Focus on what the code does, not how it does it.
Use Fixtures for Setup
Reuse setup code across multiple tests.
Testing a Calculator Class Example
Testing a shopping cart class:
class TestShoppingCart(unittest.TestCase):
def setUp(self):
self.cart = ShoppingCart()
def test_add_item_increases_count(self):
self.cart.add_item('apple', 1)
self.assertEqual(self.cart.item_count(), 1)
def test_remove_item_decreases_count(self):
self.cart.add_item('apple', 2)
self.cart.remove_item('apple', 1)
self.assertEqual(self.cart.item_count(), 1)
def test_calculate_total_with_tax(self):
self.cart.add_item('book', 10.00)
total = self.cart.calculate_total(tax_rate=0.08)
self.assertEqual(total, 10.80)
Unit testing builds confidence in your code and makes refactoring safe. Start with the most critical functions and gradually expand your test coverage.
