Testing best practices¶
Welcome to the final tutorial in the Getting Started series! You've learned to write tests and test thoroughly. Now you'll learn best practices, common mistakes to avoid, and develop good testing habits.
Time commitment: 15–20 minutes
Prerequisites: Complete Your First Test and Testing Thoroughly before starting this tutorial.
Learning objectives¶
By the end of this tutorial, you will be able to:
- Use a variety of common unittest assertions
- Recognise and avoid common testing mistakes
- Follow test naming conventions
- Write clear, maintainable tests
- Apply what you've learned in a practical exercise
Common assertions¶
You've already seen assertEqual(), assertAlmostEqual(), and assertIsNone(). Let's explore more useful assertions:
| Assertion | Checks | Example |
|---|---|---|
assertEqual(a, b) |
a == b |
self.assertEqual(add(2, 3), 5) |
assertNotEqual(a, b) |
a != b |
self.assertNotEqual(add(2, 3), 6) |
assertTrue(x) |
bool(x) is True |
self.assertTrue(result > 0) |
assertFalse(x) |
bool(x) is False |
self.assertFalse(result < 0) |
assertIn(a, b) |
a in b |
self.assertIn(3, [1, 2, 3]) |
assertIsNone(x) |
x is None |
self.assertIsNone(error_result) |
assertIsInstance(a, b) |
isinstance(a, b) |
self.assertIsInstance(result, int) |
assertAlmostEqual(a, b) |
round(a-b, 7) == 0 |
self.assertAlmostEqual(0.1 + 0.2, 0.3) |
Let's see a few more in action:
import unittest
def add(a, b):
"""Add two numbers and return the result."""
return a + b
class TestCommonAssertions(unittest.TestCase):
"""Demonstrates common assertion methods."""
def test_assertTrue_example(self):
"""Test that a condition is true."""
result = add(2, 3)
self.assertTrue(result > 0)
def test_assertIn_example(self):
"""Test that an item is in a collection."""
numbers = [1, 2, 3, 4, 5]
self.assertIn(3, numbers)
def test_assertIsInstance_example(self):
"""Test the type of an object."""
result = add(2, 3)
self.assertIsInstance(result, int)
# Run the assertion examples
unittest.main(argv=[''], verbosity=2, exit=False)
Choosing the right assertion¶
Use the most specific assertion available:
- ✅
self.assertIsNone(result)- clear intent - ❌
self.assertEqual(result, None)- less clear - ❌
self.assertTrue(result is None)- least clear
More specific assertions provide better error messages when tests fail!
Common mistakes to avoid¶
Watch out for these frequent errors as you start testing:
1. Forgetting the test_ prefix¶
def check_addition(self): # ❌ unittest won't find this
def test_addition(self): # ✅ Will run automatically
Why it matters: unittest only runs methods starting with test_. Without it, your test exists but never executes.
2. Forgetting self before assertions¶
assertEqual(result, 5) # ❌ NameError: assertEqual is not defined
self.assertEqual(result, 5) # ✅ Correct
Why it matters: Assertions are methods of the TestCase class, so you need self. to access them.
3. Comparing floats with assertEqual¶
# ❌ May fail due to floating-point precision
self.assertEqual(0.1 + 0.2, 0.3) # Actually gives 0.30000000000000004
# ✅ Use assertAlmostEqual for floats
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=7)
Why it matters: Computers store decimal numbers imperfectly. Use assertAlmostEqual when working with floats.
Let's see these mistakes in action¶
Here's code demonstrating these mistakes (intentionally broken):
class TestCommonMistakes(unittest.TestCase):
"""Demonstrates common mistakes."""
# Mistake 1: Missing 'test_' prefix - this won't run!
def check_addition(self):
"""This test will be silently ignored."""
self.assertEqual(add(2, 3), 5)
def test_addition_correct(self):
"""This test WILL run because it starts with 'test_'."""
self.assertEqual(add(2, 3), 5)
# Run the tests - notice only one test runs!
unittest.main(argv=[''], verbosity=2, exit=False)
Notice only test_addition_correct ran! The check_addition method was silently ignored.
For a complete list of common testing mistakes and how to fix them, see Avoid Common Testing Mistakes.
Test naming best practices¶
Good test names clearly describe what they're testing. Follow this pattern:
Structure: test_[function]_[scenario]_[expected_outcome]
Examples:
- ✅
test_add_positive_numbers- clear and specific - ✅
test_divide_by_zero_raises_error- describes the behaviour - ✅
test_calculate_discount_with_invalid_input_returns_none- very explicit - ❌
test1- no information about what's being tested - ❌
test_function- too vague
Why good names matter¶
When a test fails, you'll see:
FAIL: test_calculate_discount_with_invalid_input_returns_none
vs.
FAIL: test1
Which one tells you what went wrong?
For comprehensive naming conventions and patterns, see Test Naming Conventions.
Writing clear tests¶
Follow these principles for maintainable tests:
1. One test, one behaviour¶
Each test should check one specific behaviour:
# ❌ Testing multiple behaviours
def test_calculator(self):
self.assertEqual(add(2, 3), 5)
self.assertEqual(subtract(5, 3), 2)
self.assertEqual(multiply(2, 3), 6)
# ✅ Separate tests for each behaviour
def test_add_positive_numbers(self):
self.assertEqual(add(2, 3), 5)
def test_subtract_positive_numbers(self):
self.assertEqual(subtract(5, 3), 2)
Why? When a test fails, you immediately know which behaviour broke.
2. Use descriptive docstrings¶
def test_calculate_discount_invalid_negative_price(self):
"""Test that negative price returns None."""
self.assertIsNone(calculate_discount(-10, 20))
The docstring appears in test output, helping you understand what broke.
3. Keep tests simple¶
Tests should be easy to read and understand:
# ✅ Clear and simple
def test_add_positive_numbers(self):
result = add(2, 3)
self.assertEqual(result, 5)
Exercise: Your turn!¶
Now it's time to practise what you've learned. Here's a simple function that needs tests:
def multiply(a, b):
"""Multiply two numbers and return the result."""
return a * b
Your task: Complete the test class below with at least three test methods:
- Test multiplying positive numbers
- Test multiplying by zero
- Test multiplying negative numbers
Bonus challenges:
- Test the identity property (multiplying by 1)
- Test with floating-point numbers
Try it yourself in the cell below before looking at the solution!
def multiply(a, b):
"""Multiply two numbers and return the result."""
return a * b
# Write your test class here
class TestMultiplyFunction(unittest.TestCase):
"""Your tests for the multiply() function."""
def test_multiply_positive_numbers(self):
"""Test multiplying two positive numbers."""
# TODO: Test that multiply(3, 4) equals 12
# TODO: Test that multiply(7, 5) equals 35
pass
def test_multiply_by_zero(self):
"""Test multiplying any number by zero."""
# TODO: Test that multiply(5, 0) equals 0
# TODO: Test that multiply(0, 5) also equals 0
# TODO: What about multiply(0, 0)?
pass
def test_multiply_negative_numbers(self):
"""Test multiplying negative numbers."""
# TODO: What should multiply(-3, 4) equal?
# TODO: What about multiply(3, -4)?
# TODO: What about multiply(-3, -4)?
pass
Solution¶
Here's one possible solution (try yours first!):
class TestMultiplyFunctionSolution(unittest.TestCase):
"""Solution: Tests for the multiply() function."""
def test_multiply_positive_numbers(self):
"""Test multiplying two positive numbers."""
self.assertEqual(multiply(3, 4), 12)
self.assertEqual(multiply(7, 5), 35)
def test_multiply_by_zero(self):
"""Test multiplying any number by zero."""
self.assertEqual(multiply(5, 0), 0)
self.assertEqual(multiply(0, 5), 0)
self.assertEqual(multiply(0, 0), 0)
def test_multiply_negative_numbers(self):
"""Test multiplying negative numbers."""
self.assertEqual(multiply(-3, 4), -12)
self.assertEqual(multiply(3, -4), -12)
self.assertEqual(multiply(-3, -4), 12)
def test_multiply_by_one(self):
"""Bonus: Test multiplying by one (identity property)."""
self.assertEqual(multiply(5, 1), 5)
self.assertEqual(multiply(1, 5), 5)
def test_multiply_floats(self):
"""Bonus: Test multiplying floating-point numbers."""
self.assertAlmostEqual(multiply(2.5, 4.0), 10.0, places=1)
# Run the solution tests
unittest.main(argv=[''], verbosity=2, exit=False)
Quick reference¶
Here are the essentials to remember:
Basic test structure¶
import unittest
class TestMyFunction(unittest.TestCase):
def test_specific_scenario(self):
"""Clear description of what this test checks."""
result = my_function(input_value)
self.assertEqual(result, expected_output)
# For Jupyter notebooks
unittest.main(argv=[''], verbosity=2, exit=False)
Most common assertions¶
assertEqual(a, b)- checks a == bassertTrue(x)- checks bool(x) is TrueassertIsNone(x)- checks x is NoneassertAlmostEqual(a, b)- checks a ≈ b (for floats)assertIn(a, b)- checks a in b
Golden rules¶
- Test names must start with
test_ - Use
self.before assertions - One test, one behaviour
- Use descriptive names and docstrings
- Keep tests simple and readable
For a complete quick reference guide, including all assertions, test fixtures, and patterns, see unittest Quick Reference.
Key takeaways¶
Congratulations! You've completed the Getting Started series. Let's recap everything you've learned:
From Tutorial 1: Your first test¶
- Tests are classes that inherit from
unittest.TestCase - Test methods must start with
test_so unittest can find them - Assertions (like
assertEqual()) check expected behaviour
From Tutorial 2: Testing thoroughly¶
- Test multiple scenarios - normal cases, edge cases, and error conditions
- Use appropriate assertions for different types of checks
From this tutorial¶
- Follow naming conventions for clear, maintainable tests
- Avoid common mistakes like forgetting
test_orself. - One test should check one behaviour for easier debugging
- Use specific assertions for better error messages
- Keep tests simple and readable
Next steps¶
Now that you understand the fundamentals, continue building your testing skills:
Supporting resources¶
- Understanding Test Structure - Deep dive into classes,
self, andTestCase - How to Run Tests in Jupyter - Complete guide with troubleshooting
- Avoid Common Testing Mistakes - 10 common mistakes and how to fix them
- Test Naming Conventions - Comprehensive naming patterns
- unittest Quick Reference - Complete assertion guide
Practice and learn more¶
- Test your own code: Take any Python function you've written and create 3-5 tests for it
- Explore the unittest documentation: Python unittest documentation
- Continue with more tutorials: Coming soon - test fixtures, test organisation, and mocking
Challenge yourself¶
Try writing tests for these common programming tasks:
- A function that checks if a string is a palindrome
- A function that finds the maximum value in a list
- A function that validates an email address
- A function that calculates the factorial of a number
Remember: Testing is a skill that improves with practice. Every test you write makes you a better developer.
Happy testing!