Skip to content

Testing Strategies — Part 2: Advanced Testing

← Part 1: Unit Testing · Back to Overview

Learn Your Way

Read Build Watch Test Review Visualize
You are here Projects Flashcards

This part covers mocking, parametrized tests, code coverage, and common testing mistakes — the tools and techniques that take your test suite from basic to professional.

Parametrized tests — test many inputs at once

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (1, 1, 2),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
    (1.5, 2.5, 4.0),
])
def test_add(a, b, expected):
    assert add(a, b) == expected

One test function, five test cases. Each runs independently — if one fails, the others still run.

Test doubles — stub, mock, fake, spy

When your code depends on external systems (APIs, databases, file systems), you replace them with test doubles:

Type What it does Example
Stub Returns pre-programmed responses Fake API that always returns {"status": "ok"}
Mock Records calls and verifies expectations Check that send_email() was called with the right arguments
Fake Working implementation with shortcuts In-memory database instead of PostgreSQL
Spy Wraps real object, records calls Real email sender that also logs what was sent

Using unittest.mock

from unittest.mock import patch, MagicMock

# The code we are testing:
def get_weather(city):
    import requests
    response = requests.get(f"https://api.weather.com/{city}")
    return response.json()["temperature"]

# The test — mock the API call:
def test_get_weather():
    with patch("requests.get") as mock_get:
        # Configure the mock to return fake data:
        mock_get.return_value.json.return_value = {"temperature": 72}

        result = get_weather("Portland")

        assert result == 72
        mock_get.assert_called_once_with("https://api.weather.com/Portland")

Using monkeypatch (pytest's approach)

def test_get_weather(monkeypatch):
    class FakeResponse:
        def json(self):
            return {"temperature": 72}

    def fake_get(url):
        return FakeResponse()

    monkeypatch.setattr("requests.get", fake_get)

    result = get_weather("Portland")
    assert result == 72

Code coverage with pytest-cov

Coverage measures what percentage of your code is executed by tests:

# Install:
pip install pytest-cov

# Run with coverage report:
pytest --cov=myproject

# Show which lines are NOT covered:
pytest --cov=myproject --cov-report=term-missing

# Generate HTML report:
pytest --cov=myproject --cov-report=html

Example output:

----------- coverage: ... -----------
Name              Stmts   Miss  Cover   Missing
------------------------------------------------
calculator.py        10      2    80%   15-16
test_calculator.py   20      0   100%
------------------------------------------------
TOTAL                30      2    93%

Aim for 80%+ coverage on critical code. 100% coverage does not mean zero bugs — it means every line ran, not that every scenario was tested.

Common Mistakes

Testing the framework instead of your code:

# WRONG — this tests that Python's dict works, not your code:
def test_dict():
    d = {"a": 1}
    assert d["a"] == 1

Hard-coding values that match test data:

# WRONG — hard-coded to pass the test, not solve the problem:
def fizzbuzz(n):
    if n == 3: return "Fizz"    # Only works for 3, not 6, 9, 12...

# RIGHT — actual logic:
def fizzbuzz(n):
    if n % 3 == 0: return "Fizz"

Not testing edge cases: Always test: empty input, zero, negative numbers, None, very large values, and boundary conditions.


← Part 1: Unit Testing Overview