Python Series track

Week 06 · Advanced Track

Week 6 — Testing with pytest

Write professional tests with pytest. Use fixtures for setup/teardown, parametrize for data-driven tests, mock external dependencies, and measure code coverage.

Advanced Track2 min readFree lesson

Level: Advanced | Prerequisite: Week 5

Learning Goals

  • Basic usage of pytest
  • Various cases with parametrize
  • Common setup with fixture
  • Isolate external dependencies using mock

1. Why test?

  • Prevent regression (ensure functionality when code changes)
  • Serve as specification (document intent)
  • Safety net for refactoring

2. pytest basics

pip install pytest

Test files are test_*.py or *_test.py.

# test_calc.py
def add(a, b): return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
pytest                  #Run All
pytest test_calc.py     #Single File
pytest -v               # verbose
pytest -k "add"         #Name Matching

3. parametrize

Run the same test with multiple inputs.

import pytest

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

4. fixture

Common setup code.

@pytest.fixture
def sample_users():
    return [{"name": "Alice"}, {"name": "Bob"}]

def test_first(sample_users):
    assert sample_users[0]["name"] == "Alice"

def test_count(sample_users):
    assert len(sample_users) == 2

Scope (session, module, function):

@pytest.fixture(scope="module")
def expensive():
    print("Setup just once")
    return ...

5. Exception validation

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

def test_divide_by_zero():
    with pytest.raises(ValueError, match="0 division"):
        divide(10, 0)

6. mock

Fake external dependencies (API, DB).

from unittest.mock import patch, MagicMock

def get_weather(city):
    import requests
    return requests.get(f"https://api/{city}").json()

def test_get_weather():
    with patch("requests.get") as mock_get:
        mock_get.return_value.json.return_value = {"temp": 20}
        assert get_weather("seoul") == {"temp": 20}

7. Coverage

pip install pytest-cov
pytest --cov=mypackage --cov-report=term-missing

Key Examples (examples/)

File Content
01_basic_test.py Basic assert
02_parametrize.py parametrize
03_fixture.py fixture
04_mock.py unittest.mock

⚠️ Common Mistakes

  1. Test function does not start with test_ — pytest cannot find it
  2. Dependency between tests — fails if execution order changes. Should be independent
  3. Changing global state — setup/teardown with fixture
  4. Calling real external API — slow and unstable. Use mock

❓ FAQ

Q1. unittest or pytest? A. Standard is unittest. For new projects, pytest is overwhelmingly recommended (concise, powerful).

Q2. Do we have to do TDD? A. Not necessarily, but recommended. At minimum, test the core functionalities.

Q3. What about UI testing? A. Separate tools like Selenium, Playwright, etc.

📝 Exercises (exercises/)

  • exercise1.md — Intermediate OOP code testing
  • exercise2.md — Parameterize various cases
  • exercise3.md — Test API calls with mocks

Next Week

Week 7. Web Scraping

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

examples/01_basic_test.pyPython
"""Found pytest test_* functions. Execute with the pytest command."""

def add(a, b):
    return a + b

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

def test_add_negative():
    assert add(-1, 1) == 0

def test_add_zero():
    assert add(0, 0) == 0
examples/02_parametrize.pyPython
import pytest

def add(a, b):
    return a + b

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

#Assign ID
@pytest.mark.parametrize("score,grade", [
    (95, "A"),
    (85, "B"),
    (75, "C"),
    (50, "F"),
], ids=["A grade", "B grade", "C grade", "F grade"])
def test_grade(score, grade):
    def to_grade(s):
        if s >= 90: return "A"
        if s >= 80: return "B"
        if s >= 70: return "C"
        return "F"
    assert to_grade(score) == grade
examples/03_fixture.pyPython
import pytest

@pytest.fixture
def sample_users():
    print("[fixture: setup]")
    return [
        {"name": "Alice", "age": 30},
        {"name": "Bob", "age": 25},
    ]

def test_first_user(sample_users):
    assert sample_users[0]["name"] == "Alice"

def test_count(sample_users):
    assert len(sample_users) == 2

@pytest.fixture
def temp_file(tmp_path):
    p = tmp_path / "data.txt"
    p.write_text("hello", encoding="utf-8")
    return p

def test_file_content(temp_file):
    assert temp_file.read_text(encoding="utf-8") == "hello"
examples/04_mock.pyPython
from unittest.mock import patch, MagicMock

def get_weather(city):
    import requests
    return requests.get(f"https://api/{city}").json()

def test_get_weather():
    with patch("requests.get") as mock_get:
        mock_get.return_value = MagicMock(
            json=MagicMock(return_value={"temp": 20})
        )
        assert get_weather("seoul") == {"temp": 20}
        mock_get.assert_called_once_with("https://api/seoul")

Assignment

Goal

Write pytest tests for intermediate week 7 BankAccount, Stack, etc.

Requirements

  • Normal operation cases
  • Exception cases (using pytest.raises)
  • At least 5 tests

Execution

pytest test_account.py -v

Assignment 2. Various Cases with parametrize

Goal

Verify more than 10 cases with parametrize for a single function.

Example: Grade calculation function

def grade(score):
    if score >= 90: return "A"
    elif score >= 80: return "B"
    elif score >= 70: return "C"
    elif score >= 60: return "D"
    return "F"

Cases to be tested:

  • Boundary values (89, 90)
  • Negative values, over 100
  • 4-5 normal cases

Assignment 3. API Call Test with mock

Goal

Test functions that call external APIs using mock.

Function example

def get_weather(city):
    import requests
    r = requests.get(f"https://api.example.com/weather?city={city}")
    if r.status_code == 200:
        return r.json()
    return None

Scenarios to be tested

  • Normal response (200, JSON)
  • 404 response → None
  • Network error (exception)

Use patch("requests.get").

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

test_account.pyPython
"""Run with pytest test_account.py"""
import pytest

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit is positive")
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Insufficient balance")
        self.balance -= amount

@pytest.fixture
def account():
    return BankAccount("Alice", 1000)

def test_initial_balance(account):
    assert account.balance == 1000

def test_deposit(account):
    account.deposit(500)
    assert account.balance == 1500

def test_withdraw(account):
    account.withdraw(300)
    assert account.balance == 700

def test_deposit_negative_raises(account):
    with pytest.raises(ValueError, match="Positive"):
        account.deposit(-10)

def test_withdraw_overdraft(account):
    with pytest.raises(ValueError, match="Insufficient"):
        account.withdraw(2000)
test_api.pyPython
import pytest
from unittest.mock import patch, MagicMock

def get_weather(city):
    import requests
    r = requests.get(f"https://api.example.com/weather?city={city}")
    if r.status_code == 200:
        return r.json()
    return None

def test_get_weather_success():
    with patch("requests.get") as m:
        m.return_value = MagicMock(
            status_code=200,
            json=MagicMock(return_value={"temp": 20}),
        )
        assert get_weather("seoul") == {"temp": 20}

def test_get_weather_not_found():
    with patch("requests.get") as m:
        m.return_value = MagicMock(status_code=404)
        assert get_weather("xxx") is None

def test_get_weather_network_error():
    with patch("requests.get", side_effect=ConnectionError("network down")):
        with pytest.raises(ConnectionError):
            get_weather("seoul")
test_grade.pyPython
import pytest

def grade(score):
    if score >= 90: return "A"
    elif score >= 80: return "B"
    elif score >= 70: return "C"
    elif score >= 60: return "D"
    return "F"

@pytest.mark.parametrize("score,expected", [
    (100, "A"), (95, "A"), (90, "A"),
    (89, "B"), (85, "B"), (80, "B"),
    (79, "C"), (75, "C"), (70, "C"),
    (69, "D"), (65, "D"), (60, "D"),
    (59, "F"), (30, "F"), (0, "F"),
    (-10, "F"),
])
def test_grade(score, expected):
    assert grade(score) == expected

Learn this lesson with a mentor

Work through the Python Series track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now