Chapter 12: Debugging, Logging & Testing

Techniques to find and fix bugs, record events with logging, and write automated tests.

Download chapter12.py

Objectives

1. Debugging Techniques

Start with strategic print() calls:

def compute(x, y):
    print("DEBUG:", x, y)
    return x / y
compute(5, 0)  # inspect inputs

Use the built-in pdb for breakpoints:

import pdb

def buggy():
    a = 1
    pdb.set_trace()    # pause here
    b = 0
    return a / b

buggy()

2. Logging Module

A better alternative to prints; supports levels and destinations:

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%H:%M:%S"
)
logger = logging.getLogger(__name__)

logger.debug("Debugging info")
logger.info("Startup complete")
logger.warning("Low disk space")
logger.error("An error occurred")

Create file or stream handlers for flexible output:

fh = logging.FileHandler("app.log")
fh.setLevel(logging.WARNING)
logger.addHandler(fh)

3. Assertions

Use assert to check invariants during development:

def divide(a, b):
    assert b != 0, "b must not be zero"
    return a / b

Note: assert statements are removed when Python runs with -O.

4. Unit Testing with unittest

Create test cases by subclassing unittest.TestCase:

import unittest
from chapter12 import divide

class TestMath(unittest.TestCase):
    def test_divide_normal(self):
        self.assertEqual(divide(10, 2), 5)

    def test_divide_zero(self):
        with self.assertRaises(AssertionError):
            divide(5, 0)

if __name__ == "__main__":
    unittest.main()

Run tests with python -m unittest or integrate into your CI.

5. pytest Overview

pytest offers concise syntax and fixtures:

# test_chapter12.py
import pytest
from chapter12 import divide

def test_divide():
    assert divide(9, 3) == 3

def test_divide_zero():
    with pytest.raises(AssertionError):
        divide(1, 0)

Install with pip install pytest and run pytest.

Exercises

  1. Insert pdb.set_trace() into a small function and step through it.
  2. Switch print() calls to logging at various levels and inspect the log file.
  3. Add assert checks to a data-validation function.
  4. Write unittest tests for a simple calculator module (add, sub, mul, div).
  5. Try rewriting those tests in pytest style and compare.