CS 112 › Lesson 9 of 10

Testing with pytest

Lesson 9 · OKSTEM College · Associate of Science in Computer Science

Why Unit Testing?

Unit tests verify that individual functions and methods behave as specified. A passing test suite means: (a) the code meets its spec today, and (b) future changes that break existing behaviour are caught immediately.

The cost of a bug rises exponentially the later it is found. Unit tests catch bugs at the moment you write the code — far cheaper than finding them in production.

pytest Basics

Install once: pip install pytest. Write test files prefixed with test_; name test functions starting with test_. Run with pytest from the terminal.

# file: test_math_utils.py from math_utils import clamp, is_prime def test_clamp_within_range(): assert clamp(50, 0, 100) == 50 def test_clamp_below_min(): assert clamp(-10, 0, 100) == 0 def test_clamp_above_max(): assert clamp(150, 0, 100) == 100 def test_is_prime_small_primes(): for n in [2, 3, 5, 7, 11]: assert is_prime(n) def test_is_prime_composites(): for n in [4, 6, 9, 15]: assert not is_prime(n)

Fixtures & parametrize

Fixtures provide reusable setup/teardown. @pytest.mark.parametrize runs one test with many input/output pairs.

import pytest @pytest.fixture def sample_account(): from bank import BankAccount return BankAccount(1000) # fresh for every test def test_initial_balance(sample_account): assert sample_account.balance == 1000 def test_negative_balance_raises(sample_account): with pytest.raises(ValueError): sample_account.balance = -1 @pytest.mark.parametrize("n,expected", [ (2, True), (4, False), (17, True), (1, False) ]) def test_is_prime_param(n, expected): assert is_prime(n) == expected

Practice Problems

1. Write three test functions for a Stack class with push(), pop(), and is_empty().

def test_empty_stack(): s = Stack() assert s.is_empty() def test_push_pop(): s = Stack() s.push(1); s.push(2) assert s.pop() == 2 # LIFO def test_pop_empty_raises(): s = Stack() with pytest.raises(IndexError): s.pop()

2. What does pytest.raises(ValueError) do?

It's a context manager that asserts the enclosed code raises ValueError. If the exception is raised: test passes. If it's NOT raised (or a different exception is raised): test fails.

Knowledge Check

pytest discovers test functions by looking for

pytest only searches files and functions matching its naming convention.
Correct — pytest's default discovery: test_*.py files, test_* functions.
No such decorator in pytest.
That's unittest style; pytest supports it but doesn't require it.
Recap: name your files test_*.py and your functions def test_*(). Run pytest from the terminal and it finds everything.

A pytest fixture is used for

That's @pytest.mark.xfail.
Correct — fixtures create fresh objects for each test function that requests them.
That's @pytest.mark.parametrize.
That's @pytest.mark.skip or @pytest.mark.slow with custom config.
Recap: @pytest.fixture functions run before each test that declares them as a parameter. Fresh setup every time = no shared state between tests.

pytest.raises(ValueError) asserts that

That's the opposite; it asserts the exception IS raised.
Correct — if ValueError is raised the test passes; if not, it fails.
Only the specified exception type causes a pass; others still propagate.
pytest.raises is a context manager, not a function that returns a message.
Recap: always test your error paths. with pytest.raises(TypeError): verifies your validation code actually rejects bad input.

@pytest.mark.parametrize runs

It runs the test once PER input tuple.
Correct — four tuples = four separate test runs shown individually.
Parallel execution is a plugin feature (pytest-xdist), not parametrize.
parametrize always runs; it's not conditional.
Recap: @parametrize('n,expected', [(2,True),(4,False)]) gives you two test cases: test_is_prime[2-True] and test_is_prime[4-False].

The primary value of a test suite is

Tests can only show the presence of bugs, not their absence.
Correct — tests catch when a future change breaks previously working behaviour.
Tests document behaviour implicitly but don't replace API docs.
Tests add no runtime performance benefit to production code.
Recap: the main ROI of tests is regression detection. Change a function → run tests → know immediately what broke.

← PreviousNext →