parametrize — 파라미터화 테스트
@pytest.mark.parametrize는 하나의 테스트 함수를 여러 입력값으로 반복 실행합니다. 중복 코드 없이 다양한 케이스를 커버할 수 있습니다.
기본 파라미터화
import pytest
def add(a: int, b: int) -> int:
return a + b
# 단일 파라미터
@pytest.mark.parametrize("value", [1, 2, 3, -1, 0])
def test_positive_check(value):
assert isinstance(value, int)
# 다중 파라미터 (튜플로 묶음)
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(0, 0, 0),
(-1, -2, -3),
(100, 200, 300),
])
def test_add(a, b, expected):
assert add(a, b) == expected
# ids로 테스트 이름 지정
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
], ids=["positive", "zero", "cancel"])
def test_add_named(a, b, expected):
assert add(a, b) == expected
예외 테스트 파라미터화
import pytest
def divide(a: float, b: float) -> float:
if b == 0:
raise ZeroDivisionError("0으로 나눌 수 없습니다")
return a / b
# 정상 케이스
@pytest.mark.parametrize("a, b, expected", [
(10, 2, 5.0),
(9, 3, 3.0),
(0, 5, 0.0),
])
def test_divide_ok(a, b, expected):
assert divide(a, b) == pytest.approx(expected)
# 예외 케이스
@pytest.mark.parametrize("a, b", [
(1, 0),
(100, 0),
(-5, 0),
])
def test_divide_zero(a, b):
with pytest.raises(ZeroDivisionError):
divide(a, b)
# 정상/예외 혼합 — None으로 예외 없음 표시
@pytest.mark.parametrize("a, b, expected, exc", [
(10, 2, 5.0, None),
(0, 5, 0.0, None),
(1, 0, None, ZeroDivisionError),
])
def test_divide_mixed(a, b, expected, exc):
if exc:
with pytest.raises(exc):
divide(a, b)
else:
assert divide(a, b) == pytest.approx(expected)
중첩 파라미터화 (Cartesian Product)
import pytest
# 두 파라미터의 모든 조합: 3 × 3 = 9개 테스트
@pytest.mark.parametrize("x", [1, 2, 3])
@pytest.mark.parametrize("y", [10, 20, 30])
def test_multiply(x, y):
result = x * y
assert result == x * y
assert result > 0
# 연산자 파라미터화
import operator
@pytest.mark.parametrize("op, a, b, expected", [
(operator.add, 1, 2, 3),
(operator.sub, 5, 3, 2),
(operator.mul, 4, 3, 12),
])
@pytest.mark.parametrize("scale", [1, 10])
def test_operation_scaled(op, a, b, expected, scale):
result = op(a * scale, b * scale)
assert result == expected * scale
pytest.mark — 테스트 마킹
import pytest
import time
# skip: 무조건 건너뜀
@pytest.mark.skip(reason="아직 미구현")
def test_not_implemented():
assert False # 실행되지 않음
# skipif: 조건부 건너뜀
import sys
@pytest.mark.skipif(sys.platform == "win32", reason="Windows 미지원")
def test_unix_only():
assert True # Windows에서는 skip
# 버전 조건
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Python 3.10+ 필요")
def test_match_statement():
value = 42
match value:
case 42:
result = "answer"
case _:
result = "unknown"
assert result == "answer"
# xfail: 실패 예상 (실패해도 전체가 FAIL이 되지 않음)
@pytest.mark.xfail(reason="알려진 버그 #123")
def test_known_bug():
assert 1 + 1 == 3 # 예상된 실패 → XFAIL
# xfail strict: 의도와 반대로 통과하면 FAIL
@pytest.mark.xfail(strict=True, reason="고쳐졌어야 함")
def test_should_still_fail():
assert 1 + 1 == 3 # 실패 → XFAIL (통과하면 XPASS → FAIL)
# custom 마크
@pytest.mark.slow
def test_long_running():
time.sleep(0.1)
assert True
@pytest.mark.integration
def test_integration():
assert True
파라미터화 + 마킹 조합
import pytest
import sys
@pytest.mark.parametrize("input_val, expected", [
(0, True),
(1, False),
pytest.param(-1, False, id="negative"),
pytest.param(
999, True,
marks=pytest.mark.skip(reason="엣지 케이스 미검증"),
),
pytest.param(
"abc", None,
marks=pytest.mark.xfail(raises=TypeError),
),
])
def test_is_zero(input_val, expected):
assert (input_val == 0) == expected
# 조건부 skip을 파라미터에 직접 적용
@pytest.mark.parametrize("platform, expected", [
pytest.param("linux", True, marks=pytest.mark.skipif(
sys.platform != "linux", reason="Linux only"
)),
("win32", True),
("darwin", True),
])
def test_platform(platform, expected):
assert expected is True
실전 패턴: 입력 검증 테스트
import pytest
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
def __post_init__(self):
if not self.name:
raise ValueError("이름은 비어 있을 수 없습니다")
if not (0 <= self.age <= 150):
raise ValueError(f"유효하지 않은 나이: {self.age}")
if "@" not in self.email:
raise ValueError(f"유효하지 않은 이메일: {self.email}")
# 유효한 케이스
@pytest.mark.parametrize("name, age, email", [
("Alice", 30, "alice@example.com"),
("Bob", 0, "bob@test.org"),
("Charlie", 150, "c@d.io"),
])
def test_valid_user(name, age, email):
user = User(name=name, age=age, email=email)
assert user.name == name
# 유효하지 않은 케이스
@pytest.mark.parametrize("name, age, email, error_msg", [
("", 30, "a@b.com", "이름은 비어 있을 수 없습니다"),
("Alice", -1, "a@b.com", "유효하지 않은 나이"),
("Alice", 151, "a@b.com", "유효하지 않은 나이"),
("Alice", 30, "invalid", "유효하지 않은 이메일"),
])
def test_invalid_user(name, age, email, error_msg):
with pytest.raises(ValueError, match=error_msg):
User(name=name, age=age, email=email)
정리
| 기능 | 사용법 |
|---|---|
| 기본 파라미터화 | @pytest.mark.parametrize("x", [1, 2, 3]) |
| 다중 파라미터 | @pytest.mark.parametrize("a, b", [(1, 2), (3, 4)]) |
| 테스트 ID 지정 | ids=["case1", "case2"] 또는 pytest.param(..., id="name") |
| 중첩 파라미터 | 데코레이터 중첩 → 카테시안 곱 |
| 케이스별 마크 | pytest.param(..., marks=pytest.mark.skip(...)) |
| 건너뜀 | @pytest.mark.skip / @pytest.mark.skipif |
| 예상 실패 | @pytest.mark.xfail |
| 커스텀 마크 | @pytest.mark.slow + pytest.ini markers 등록 |
파라미터화는 동일한 로직을 다양한 입력으로 검증할 때 가장 강력합니다.