unittest Quick Reference¶
A concise reference guide for Python's unittest module. Keep this handy while writing tests.
Basic Test Structure¶
import unittest
class TestMyFunction(unittest.TestCase):
"""Tests for my_function."""
def test_specific_scenario(self):
"""Clear description of what this test checks."""
# Arrange: Set up test data
input_value = 42
# Act: Call the function
result = my_function(input_value)
# Assert: Check the result
self.assertEqual(result, expected_output)
# For Jupyter notebooks
unittest.main(argv=[''], verbosity=2, exit=False)
# For .py files
if __name__ == '__main__':
unittest.main()
Common Assertions¶
| Assertion | Checks | Example |
|---|---|---|
assertEqual(a, b) |
a == b | self.assertEqual(add(2,3), 5) |
assertNotEqual(a, b) |
a != b | self.assertNotEqual(result, 0) |
assertTrue(x) |
bool(x) is True | self.assertTrue(len(items) > 0) |
assertFalse(x) |
bool(x) is False | self.assertFalse(is_empty(data)) |
assertIs(a, b) |
a is b | self.assertIs(result, None) |
assertIsNot(a, b) |
a is not b | self.assertIsNot(new_obj, old_obj) |
assertIsNone(x) |
x is None | self.assertIsNone(find_user('')) |
assertIsNotNone(x) |
x is not None | self.assertIsNotNone(result) |
assertIn(a, b) |
a in b | self.assertIn('error', message) |
assertNotIn(a, b) |
a not in b | self.assertNotIn('debug', log) |
assertIsInstance(a, b) |
isinstance(a, b) | self.assertIsInstance(result, list) |
assertNotIsInstance(a, b) |
not isinstance(a, b) | self.assertNotIsInstance(x, str) |
Numeric Assertions¶
| Assertion | Use Case | Example |
|---|---|---|
assertAlmostEqual(a, b) |
Floats (7 decimal places) | self.assertAlmostEqual(0.1+0.2, 0.3) |
assertAlmostEqual(a, b, places=n) |
Floats (n decimal places) | self.assertAlmostEqual(pi, 3.14, places=2) |
assertNotAlmostEqual(a, b) |
Floats are different | self.assertNotAlmostEqual(0.1, 0.2) |
assertGreater(a, b) |
a > b | self.assertGreater(score, 0) |
assertGreaterEqual(a, b) |
a >= b | self.assertGreaterEqual(count, 1) |
assertLess(a, b) |
a < b | self.assertLess(error_rate, 0.01) |
assertLessEqual(a, b) |
a <= b | self.assertLessEqual(age, 120) |
Collection Assertions¶
| Assertion | Checks | Example |
|---|---|---|
assertListEqual(a, b) |
Lists are equal | self.assertListEqual([1,2], result) |
assertTupleEqual(a, b) |
Tuples are equal | self.assertTupleEqual((1,2), coords) |
assertSetEqual(a, b) |
Sets are equal | self.assertSetEqual({1,2}, set(result)) |
assertDictEqual(a, b) |
Dicts are equal | self.assertDictEqual({'a': 1}, result) |
assertCountEqual(a, b) |
Same elements, any order | self.assertCountEqual([1,2,3], [3,2,1]) |
Exception and Warning Assertions¶
Testing Exceptions¶
# Method 1: Context manager (recommended)
def test_divide_by_zero_raises_error(self):
"""Test that dividing by zero raises ZeroDivisionError."""
with self.assertRaises(ZeroDivisionError):
divide(10, 0)
# Method 2: With exception object
def test_invalid_input_error_message(self):
"""Test that error message is correct."""
with self.assertRaises(ValueError) as cm:
parse_age("invalid")
self.assertEqual(str(cm.exception), "Age must be a number")
# Method 3: Callable form
def test_exception_with_args(self):
"""Test exception with specific arguments."""
self.assertRaises(ValueError, parse_age, "invalid")
Testing Warnings¶
def test_deprecated_function_warns(self):
"""Test that deprecated function issues warning."""
with self.assertWarns(DeprecationWarning):
old_function()
String Assertions¶
| Assertion | Checks | Example |
|---|---|---|
assertRegex(text, pattern) |
Regex matches | self.assertRegex(email, r'.*@.*\.com') |
assertNotRegex(text, pattern) |
Regex doesn't match | self.assertNotRegex(id, r'[^0-9]') |
assertMultiLineEqual(a, b) |
Multiline strings equal | self.assertMultiLineEqual(output, expected) |
Test Fixtures¶
setUp and tearDown¶
class TestDatabase(unittest.TestCase):
def setUp(self):
"""Runs before each test method."""
self.db = create_test_database()
self.db.connect()
def tearDown(self):
"""Runs after each test method."""
self.db.disconnect()
self.db.delete()
def test_insert(self):
"""self.db is available here."""
self.db.insert("data")
self.assertEqual(self.db.count(), 1)
setUpClass and tearDownClass¶
class TestExpensiveSetup(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Runs once before all tests in the class."""
cls.shared_resource = create_expensive_resource()
@classmethod
def tearDownClass(cls):
"""Runs once after all tests in the class."""
cls.shared_resource.cleanup()
def test_something(self):
"""Can access self.shared_resource."""
result = self.shared_resource.process()
self.assertIsNotNone(result)
Test Discovery¶
Running Tests¶
# Run all tests in current directory
python -m unittest discover
# Run all tests in specific directory
python -m unittest discover -s tests
# Run all tests with verbose output
python -m unittest discover -v
# Run specific test file
python -m unittest test_calculator
# Run specific test class
python -m unittest test_calculator.TestAddition
# Run specific test method
python -m unittest test_calculator.TestAddition.test_positive_numbers
Skipping Tests¶
import unittest
class TestFeature(unittest.TestCase):
@unittest.skip("Not implemented yet")
def test_future_feature(self):
pass
@unittest.skipIf(sys.version_info < (3, 10), "Requires Python 3.10+")
def test_new_syntax(self):
pass
@unittest.skipUnless(has_database(), "Requires database")
def test_database_operation(self):
pass
Subtest¶
For testing multiple related scenarios:
def test_multiple_inputs(self):
"""Test function with various inputs."""
test_cases = [
(2, 3, 5),
(10, 5, 15),
(-1, 1, 0),
]
for a, b, expected in test_cases:
with self.subTest(a=a, b=b):
result = add(a, b)
self.assertEqual(result, expected)
Custom Failure Messages¶
def test_with_custom_message(self):
"""Test with helpful failure message."""
result = calculate(10, 20)
self.assertEqual(
result,
30,
f"Expected 30 but got {result} when adding 10 and 20"
)
Assertion Aliases¶
Some assertions have multiple names:
| Primary | Aliases |
|---|---|
assertEqual |
assertEquals (deprecated) |
assertNotEqual |
assertNotEquals (deprecated) |
assertRaises |
assertRaisesRegex (with regex) |
Note: Use the non-deprecated forms (without 's' at the end).
Test Naming Convention¶
Examples:
- test_add_positive_numbers_returns_sum
- test_divide_by_zero_returns_none
- test_empty_input_raises_valueerror
Golden Rules¶
- Test methods must start with
test_or they won't run - Use
self.before all assertions (e.g.,self.assertEqual) - One test should test one behaviour
- Use
assertAlmostEqualfor floats, notassertEqual - Write descriptive test names and docstrings
- Test edge cases: zero, negative, empty, None, boundaries
- Every test should pass, every time
Common Patterns¶
Arrange-Act-Assert¶
def test_withdrawal(self):
"""Test bank account withdrawal."""
# Arrange
account = BankAccount(balance=100)
# Act
account.withdraw(30)
# Assert
self.assertEqual(account.balance, 70)
Testing with Mock Data¶
def test_process_user(self):
"""Test user processing with sample data."""
user = {
'name': 'Test User',
'email': 'test@example.com',
'age': 25
}
result = process_user(user)
self.assertTrue(result['verified'])
self.assertEqual(result['status'], 'active')
Debugging Failed Tests¶
When a test fails, check:
- Read the assertion error - it tells you what went wrong
- Check the test name and docstring - ensure you're testing what you think
- Verify test data - are inputs correct?
- Run the test in isolation - does it still fail?
- Add print statements - see what values you're getting
- Use a debugger - step through the code
Next Steps¶
- Your First Test Tutorial
- Test Naming Conventions
- Avoiding Common Mistakes
- Understanding Test Structure