← 파이썬 강의 목록으로
🚀
고급 (Advanced)
pytest · parametrize · fixture · mock

6주차 — 테스트 (unittest, pytest)

pytest 기본 사용법, parametrize 로 다양한 케이스, fixture 로 공통 setup, 그리고 mock 으로 외부 의존성을 격리하는 방법까지 다룹니다.

pytestfixtureparametrizemock
소요 시간
2시간
난이도
📊 고급
선수 조건
🎯 고급 5주차
결과물
pytest 로 자동화된 테스트 작성

이 강의에서 배우는 것

  • 1pytest 기본 사용법을 익힌다
  • 2parametrize 로 다양한 케이스를 테스트한다
  • 3fixture 로 공통 setup 을 제공한다
  • 4mock 으로 외부 의존성을 격리한다

1. 왜 테스트?

  • 회귀 방지 — 코드 변경 시 동작 보장
  • 명세 역할 — 의도 문서화
  • 리팩토링 안전망

2. pytest 기본

bash
pip install pytest

테스트 파일은 test_*.py 또는 *_test.py 입니다.

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

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
bash
pytest                  # 전체 실행
pytest test_calc.py     # 한 파일
pytest -v               # verbose
pytest -k "add"         # 이름 매칭

3. parametrize

같은 테스트를 여러 입력으로.

python
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

python
@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

스코프(session, module, function):

python
@pytest.fixture(scope="module")
def expensive():
    print("한 번만 setup")
    return ...

5. 예외 검증

python
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

외부 의존성(API, DB)을 가짜로.

python
from unittest.mock import patch

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. 커버리지

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

자주 하는 실수

  1. 테스트 함수가 test_ 로 시작 안 함 — pytest가 못 찾음
  2. 테스트끼리 의존 — 실행 순서가 바뀌면 깨짐. 독립적으로
  3. 전역 상태 변경 — fixture로 setup/teardown
  4. 외부 API 진짜로 호출 — 느리고 불안정. mock 사용

FAQ

Q1. unittest vs pytest? — 표준은 unittest. 신규는 pytest 압도적 권장.

Q2. TDD 를 꼭? — 아니지만 권장. 핵심 기능은 테스트.

Q3. UI 테스트는? — Selenium, Playwright 등 별도.

💻 예제 (examples)

실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.

01_basic_test.py기본 assert
CODE
def add(a, b):
    return a + b

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

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

# 실행: pytest 01_basic_test.py -v
▶ 실행 결과
01_basic_test.py::test_add_simple PASSED
01_basic_test.py::test_add_negative PASSED
2 passed in 0.01s
02_parametrize.pyparametrize
CODE
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),
])
def test_add(a, b, expected):
    assert add(a, b) == expected

# 실행: pytest 02_parametrize.py -v
▶ 실행 결과
test_add[1-2-3] PASSED
test_add[-1-1-0] PASSED
test_add[0-0-0] PASSED
test_add[100-200-300] PASSED
4 passed in 0.02s
03_fixture.pyfixture
CODE
import pytest

@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

# 실행: pytest 03_fixture.py -v
▶ 실행 결과
test_first PASSED
test_count PASSED
2 passed in 0.01s
04_mock.pyunittest.mock
CODE
from unittest.mock import patch

def get_temp(city):
    import requests
    r = requests.get(f"https://api/{city}")
    return r.json()["temp"]

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

# 실행: pytest 04_mock.py -v
▶ 실행 결과
test_get_temp PASSED
1 passed in 0.05s

📝 과제 (exercises)

직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.

과제 1

단위 테스트 5개

목표: 간단한 함수에 대해 테스트 5개를 작성한다.

요구사항
  • is_even(n) -> bool 에 대해 짝수/홀수/0 케이스
  • sum_list(xs) 에 대해 빈/일반 케이스
💡 힌트

pytest 명령어로 실행

입출력 예시
5 passed
채점
  • · test_ 함수
  • · 5개 PASS
정답 코드 펼치기 / 접기
SOLUTION
# test_basics.py
def is_even(n):
    return n % 2 == 0

def sum_list(xs):
    return sum(xs)

def test_is_even_true():
    assert is_even(4) is True

def test_is_even_false():
    assert is_even(7) is False

def test_is_even_zero():
    assert is_even(0) is True

def test_sum_empty():
    assert sum_list([]) == 0

def test_sum_basic():
    assert sum_list([1, 2, 3]) == 6

# pytest test_basics.py -v
▶ 실행 결과
5 passed in 0.02s
과제 2

parametrize 테이블

목표: parametrize 로 6개 케이스 테스트.

요구사항
  • max_of(a, b) 에 대해 6개 (a, b, expected) 케이스
💡 힌트

@pytest.mark.parametrize

입출력 예시
6 passed
채점
  • · 6개 케이스
  • · 모두 통과
정답 코드 펼치기 / 접기
SOLUTION
import pytest

def max_of(a, b):
    return a if a > b else b

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 2),
    (5, 3, 5),
    (-1, -2, -1),
    (0, 0, 0),
    (100, 99, 100),
    (-5, 0, 0),
])
def test_max_of(a, b, expected):
    assert max_of(a, b) == expected

# pytest test_max.py -v
▶ 실행 결과
6 passed in 0.02s
과제 3

mock 으로 API 테스트

목표: 외부 호출을 mock 으로 대체한다.

요구사항
  • fetch_user(uid) 가 requests.get 사용
  • patch 로 결과 가짜
💡 힌트

mock_get.return_value.json.return_value

입출력 예시
1 passed
채점
  • · patch 사용
  • · 테스트 PASS
정답 코드 펼치기 / 접기
SOLUTION
from unittest.mock import patch

def fetch_user(uid):
    import requests
    return requests.get(f"https://api/users/{uid}").json()

def test_fetch_user():
    with patch("requests.get") as mock_get:
        mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
        result = fetch_user(1)
    assert result == {"id": 1, "name": "Alice"}

# pytest test_user.py -v
▶ 실행 결과
1 passed in 0.05s
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗