CODE: Python Unit Testing

Testing in Python verifies that functions and data structures behave correctly, using unit tests, integration tests, and automation frameworks like pytest or unittest. Debugging identifies why logic or state breaks at runtime, using tools like pdb, stack traces, and print-based inspection to fix issues with precision.

Testing & Debugging in Python (unittest, pytest, pdb)

Testing ensures your code does what you think it does; debugging helps you figure out why it doesn’t. You’ll use:

  • unittest: built-in xUnit-style framework.
  • pytest: popular, concise, powerful.
  • pdb: interactive debugger (also breakpoint()).

`unittest` (Standard Library)

Basic structure

bash
project/
└─ src/
   └─ mathy.py
└─ tests/
   └─ test_mathy_unittest.py

python
# src/mathy.py
def add(a, b): return a + b
def div(a, b): return a / b

python
# tests/test_mathy_unittest.py
import unittest
from src.mathy import add, div

class TestMathy(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)

    def test_div_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            div(1, 0)

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

Run: python -m unittest -v

Handy asserts: assertEqualassertTrue/FalseassertIsNoneassertInassertAlmostEqualassertRaises.

Fixtures: override setUptearDown, or class-level setUpClasstearDownClass.


 `pytest` (Concise & Powerful)

Install: python -m pip install pytest

Conventions

  • Files named test_.py or _test.py
  • Test functions: def test_...():
  • Simple assert statements.

python
# tests/test_mathy_pytest.py
from src.mathy import add, div
import pytest

def test_add():
    assert add(2, 3) == 5

def test_div_by_zero():
    with pytest.raises(ZeroDivisionError):
        div(1, 0)

Run: pytest -q

Useful flags: -vv verbose, -k expr filter, -x stop on first fail, -s show print output, --maxfail=1.

Parametrization

python
import pytest
from src.mathy import add

@pytest.mark.parametrize("a,b,res", [(1,2,3),(0,0,0),(-1,1,0)])
def test_add_param(a,b,res):
    assert add(a,b) == res

Fixtures (setup/teardown without classes)

python
# tests/conftest.py
import tempfile, shutil, pytest, pathlib

@pytest.fixture
def tempdir():
    d = pathlib.Path(tempfile.mkdtemp())
    yield d
    shutil.rmtree(d)

python
# tests/test_files.py
def test_writes_file(tempdir):
    p = tempdir / "out.txt"
    p.write_text("hi")
    assert p.read_text() == "hi"

Built-ins you’ll love: tmp_pathmonkeypatchcapsys (capture stdout/stderr).

Mocking

pytest plays well with stdlib unittest.mock:

python
from unittest.mock import patch
from src.mathy import div

@patch("src.mathy.div", return_value=10)
def test_stubbed_div(mock_div):
    assert div(100, 5) == 10
    mock_div.assert_called_once()

Or patch env/IO with monkeypatch:

python
def test_env(monkeypatch):
    monkeypatch.setenv("API_TOKEN", "test")

Configuration

Create pytest.ini:

ini
[pytest]
addopts = -vv --strict-markers
testpaths = tests

Coverage

python
python -m pip install pytest-cov
pytest --cov=src --cov-report=term-missing


Debugging with `pdb` (and `breakpoint()`)

Quick use

python
# anywhere in code or tests
breakpoint()  # Python 3.7+: enters debugger (pdb by default)

Run your script/tests, execution stops at the breakpoint.

Common commands

  • n next line
  • s step into
  • c continue
  • l list source
  • p expr print
  • pp expr pretty-print
  • u/d up/down stack
  • bt backtrace
  • q quit

Drop into pdb on failure

  • pytest -x --pdb → stop at first failure in debugger
  • Or post-mortem:
  • python
    import pdb, traceback, sys
    try:
        risky()
    except Exception:
        traceback.print_exc()
        pdb.post_mortem()

    Use another debugger (optional)

Set PYTHONBREAKPOINT=ipdb.set_trace if using ipdb.


Choosing between `unittest` and `pytest`

  • Start with pytest for most projects: fewer lines, rich ecosystem.
  • Use unittest if you must stay stdlib-only or integrate with legacy test suites.
  • You can mix them: pytest can run unittest tests.

Test structure (suggested)

bash
project/
├─ src/
│  └─ ...
├─ tests/
│  ├─ conftest.py        # shared fixtures
│  ├─ test_api.py
│  ├─ test_mathy.py
│  └─ test_integration.py
├─ requirements.txt
└─ pytest.ini

Types of tests

  • Unit: small, isolated (fast, mocked I/O).
  • Integration: components together (DB/API).
  • End-to-end: full flow (slow, few).

Aim for fast, deterministic unit tests; run them on every change.


Quick patterns & tips

  • AAA pattern: Arrange, Act, Assert.
  • Name tests by behavior: test_add_handles_negatives.
  • Small steps: one assertion focus; failure message should be obvious.
  • Use factories/fixtures to avoid repetitive setup.
  • Mock boundaries (network, filesystem, time).
  • Randomness/time: seed PRNG, freeze/fake time where needed.
  • Flaky tests: remove nondeterminism; as last resort mark @pytest.mark.flaky.

Mini “cheat sheet”

python
# unittest
python -m unittest -v

# pytest basics
pytest -q
pytest -vv -k add            # run tests matching 'add'
pytest -x                    # stop on first failure
pytest --maxfail=1 --pdb     # drop into debugger on failure
pytest --cov=src --cov-report=term-missing

# run a single test file / node
pytest tests/test_mathy_pytest.py::test_add