Course Progress93%
🍎 Python Databases & Testing Topic 93 / 100
⏳ 8 min read

Writing Unit Tests

Test your functions with Python’s built-in unittest — write tests once, run them every time, and catch bugs the moment they appear.

"Untested code is broken code you haven't found yet. A test suite is a safety net — it catches bugs the moment they're introduced, not six months later in production."

— Shurai

Why Write Tests at All?

✗ Without tests
Every change is a gamble
Bugs found by users
Refactoring feels risky
Manual checking after every edit
✓ With tests
Bugs caught instantly
Refactor confidently
Tests document behaviour
Run 100 checks in 0.1 seconds

The Code to Test

Let’s build a small math_utils.py module and write tests for it:

python — math_utils.py (the code we're testing)
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

def is_even(n):
    return n % 2 == 0

Writing Tests with unittest

A test file follows three rules: import unittest, create a class that inherits from unittest.TestCase, write methods starting with test_:

python — test_math_utils.py
import unittest
from math_utils import add, subtract, divide, is_even

class TestMathUtils(unittest.TestCase):

    def test_add_two_positives(self):
        self.assertEqual(add(2, 3), 5)       # expected 5, got ?

    def test_add_negative_numbers(self):
        self.assertEqual(add(-3, -2), -5)

    def test_add_zero(self):
        self.assertEqual(add(7, 0), 7)

    def test_divide_normal(self):
        self.assertEqual(divide(10, 2), 5.0)

    def test_divide_by_zero_raises(self):
        # assertRaises checks that the exception IS raised
        with self.assertRaises(ValueError):
            divide(5, 0)

    def test_is_even_true(self):
        self.assertTrue(is_even(4))
        self.assertTrue(is_even(0))

    def test_is_even_false(self):
        self.assertFalse(is_even(7))

if __name__ == "__main__":
    unittest.main()
terminal — run the tests
python -m unittest test_math_utils.py -v

# test_add_negative_numbers ... ok
# test_add_two_positives ....... ok
# test_add_zero ................ ok
# test_divide_by_zero_raises ... ok
# test_divide_normal ........... ok
# test_is_even_false ........... ok
# test_is_even_true ............ ok
# -----------------------------------------------
# Ran 7 tests in 0.002s
# OK

The Assert Methods — Cheat Sheet

Method Passes when… Use for
assertEqual(a, b)a == bMost return values
assertNotEqual(a, b)a != bResult should have changed
assertTrue(x)x is truthyBoolean checks
assertFalse(x)x is falsyBoolean checks
assertIsNone(x)x is NoneOptional return values
assertIn(a, b)a in bLists, strings, dicts
assertRaises(Exc)code raises ExcError handling paths
Name tests to describe the scenario, not just the function

test_divide_by_zero_raises is better than test_divide2. When a test fails, its name should immediately tell you what scenario broke — saving you from opening the file just to understand what went wrong.

"Write tests for the happy path (normal inputs), the edge cases (zero, empty string, None), and the error paths (what should raise an exception). Cover those three and you'll catch 90% of bugs."

— Shurai

🧠 Quiz — Q1

What are the three rules for writing a unittest test file?

🧠 Quiz — Q2

How do you test that a function raises a specific exception?

🧠 Quiz — Q3

A test method is named test_divide_by_zero_raises. Why is this a good name?

🧠 Quiz — Q4

What does python -m unittest test_math.py -v do differently from without -v?