파이썬 pytest 단위 테스트 기초: Fixture와 테스트 자동화

파이썬 pytest로 단위 테스트를 작성하고 Fixture로 준비·정리 코드를 재사용하는 방법, parametrize와 예외·로그 테스트, 자동 실행 구성을 예제로 설명합니다.

본문 상단 광고 구역 (승인 후 자동 노출됩니다)
파이썬 pytest 단위 테스트와 자동화 결과를 보여주는 개발 환경
파이썬 pytest Fixture·단위 테스트 자동화 가이드

파이썬 단위 테스트를 빠르게 시작하려면 pytest를 설치하고, 이름이 test_로 시작하는 함수 안에서 일반 assert문으로 기대 결과를 확인하면 됩니다. 여러 테스트가 공통으로 사용하는 준비 데이터는 Fixture로 분리하고, 같은 로직을 여러 입력값으로 검증할 때는 @pytest.mark.parametrize를 사용하면 중복 코드를 크게 줄일 수 있습니다.

pytest 공식 시작 가이드 확인하기

핵심 요약

  • pip install -U pytest로 설치하고 프로젝트 루트에서 pytest를 실행합니다.
  • 테스트 파일은 test_*.py 또는 *_test.py, 테스트 함수는 test_로 시작해야 자동 수집됩니다.
  • Fixture는 준비·정리 코드를 재사용하고 테스트 함수가 필요한 자원을 매개변수처럼 받게 합니다.
  • parametrize는 입력값과 예상값 묶음을 반복 실행해 경계값을 빠짐없이 검사하는 데 적합합니다.
  • 실패를 숨기지 말고 테스트 이름, 실패 메시지, 실행 범위를 읽기 쉽게 유지해야 자동화 효과가 커집니다.

pytest 설치와 첫 단위 테스트 작성

pytest는 파이썬 표준 라이브러리가 아니므로 프로젝트의 가상환경에 설치합니다. 설치한 환경과 코드를 실행하는 환경이 다르면 명령을 찾지 못하거나 패키지를 불러오지 못할 수 있습니다. 터미널에서 파이썬 경로와 pytest 버전을 함께 확인하면 환경 혼선을 줄일 수 있습니다.

python -m pip install -U pytest
python -m pytest --version

다음처럼 계산 함수와 테스트 파일을 분리해 보겠습니다.

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

# test_calculator.py
from calculator import add

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

프로젝트 루트에서 python -m pytest를 실행하면 pytest가 규칙에 맞는 파일과 함수를 찾아 실행합니다. 실패하면 단순히 실패 여부만 보여주는 것이 아니라 비교식의 실제 값과 기대값을 자세히 표시합니다. 테스트 하나는 가능한 한 하나의 동작을 분명하게 검증하고, 이름만 읽어도 조건과 기대 결과가 드러나게 작성하세요.

테스트 파일 구조와 필요한 테스트만 실행하는 법

작은 프로젝트는 코드 옆에 테스트 파일을 둘 수 있지만, 규모가 커지면 루트에 tests 폴더를 만들어 기능별로 나누는 편이 관리하기 쉽습니다. 애플리케이션 코드와 테스트 코드의 import 경로가 꼬이지 않도록 가상환경에서 프로젝트 루트를 기준으로 실행하는 습관을 들이세요.

project/
├─ app/
│  └─ calculator.py
├─ tests/
│  ├─ test_calculator.py
│  └─ test_api.py
└─ pyproject.toml
명령 실행 범위
pytest 현재 위치 아래의 전체 테스트
pytest tests/test_calculator.py 특정 파일
pytest tests/test_calculator.py::test_add_two_positive_numbers 특정 함수
pytest -k add 이름 표현식과 일치하는 테스트
pytest -q 간단한 출력으로 전체 실행
pytest -x 첫 실패에서 중단

테스트가 많아지면 개발 중에는 관련 파일이나 함수만 실행하고, 코드 반영 전에는 전체 테스트를 다시 실행합니다. -x는 첫 오류를 빠르게 고칠 때 유용하지만 뒤쪽 실패를 숨길 수 있으므로 최종 확인에서는 빼는 것이 좋습니다.

Fixture로 준비와 정리 코드를 재사용하기

Fixture는 테스트에 필요한 객체, 임시 파일, 데이터베이스 연결 같은 준비 작업을 함수로 분리하는 기능입니다. 테스트 함수의 매개변수 이름과 Fixture 함수 이름을 맞추면 pytest가 실행 순서를 관리하고 반환값을 전달합니다. 각 테스트가 독립된 새 데이터를 받게 만들면 실행 순서 때문에 결과가 바뀌는 문제를 막을 수 있습니다.

import pytest

@pytest.fixture
def user_data():
    return {
        "name": "홍길동",
        "age": 30,
        "active": True,
    }

def test_active_user_name(user_data):
    assert user_data["active"] is True
    assert user_data["name"] == "홍길동"

yield로 테스트 후 정리하기

파일이나 연결처럼 사용 후 정리가 필요한 자원은 Fixture 안에서 yield 앞에 준비 코드를, 뒤에 정리 코드를 둡니다. 테스트가 실패해도 정리 단계가 실행되므로 직접 삭제 코드를 각 테스트에 반복하는 것보다 안전합니다.

import pytest

@pytest.fixture
def sample_file(tmp_path):
    path = tmp_path / "sample.txt"
    path.write_text("pytest", encoding="utf-8")
    yield path
    # tmp_path 자원은 pytest가 관리합니다.

def test_sample_file(sample_file):
    assert sample_file.read_text(encoding="utf-8") == "pytest"

Fixture의 기본 범위는 함수 단위입니다. scope="module"이나 scope="session"은 생성 비용을 줄일 수 있지만 상태가 테스트 사이에 공유됩니다. 변경 가능한 객체나 데이터베이스 데이터라면 속도를 이유로 범위를 무작정 넓히지 말고 격리 여부부터 확인하세요. 여러 테스트 파일이 공유하는 Fixture는 conftest.py에 둘 수 있으며 별도 import 없이 검색됩니다.

parametrize로 여러 입력값을 한 번에 검증하기

하나의 함수에 정상값, 경계값, 잘못된 값처럼 여러 사례를 적용할 때 테스트 함수를 복사하지 말고 매개변수화합니다. 각 튜플은 독립된 테스트 사례로 수집되므로 어떤 입력에서 실패했는지 결과에서 확인할 수 있습니다.

import pytest
from calculator import add

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

값 묶음이 많을수록 의미를 알아보기 어려워질 수 있습니다. 필요한 경우 idspytest.param(..., id="negative")을 사용해 사례 이름을 붙이세요. 다만 서로 다른 기능까지 하나의 거대한 매개변수 테스트에 넣으면 실패 원인을 읽기 어려우므로 검색 의도와 동작 단위별로 나누는 편이 낫습니다.

예외, 로그, 외부 의존성은 어떻게 테스트할까요?

예상한 예외 확인

오류가 발생하는 것이 올바른 동작인 입력은 pytest.raises()로 확인합니다. 아무 예외나 허용하지 말고 기대하는 구체적인 예외 클래스를 지정하세요.

import pytest

def divide(a, b):
    if b == 0:
        raise ValueError("0으로 나눌 수 없습니다.")
    return a / b

def test_divide_by_zero():
    with pytest.raises(ValueError, match="0으로 나눌 수 없습니다"):
        divide(10, 0)

로그 메시지 확인

운영 코드가 중요한 경고를 남기는지 확인할 때 pytest의 caplog Fixture를 사용할 수 있습니다. 단순히 로그 문자열 전체를 고정하면 문구 수정만으로 테스트가 깨질 수 있으므로 로그 수준과 핵심 식별자처럼 중요한 부분을 검사하세요.

import logging

def warn_low_stock(count):
    if count < 3:
        logging.warning("재고 부족: %s", count)

def test_low_stock_log(caplog):
    with caplog.at_level(logging.WARNING):
        warn_low_stock(2)
    assert "재고 부족" in caplog.text

네트워크 API, 현재 시간, 환경 변수처럼 외부 상태에 의존하는 코드는 실제 서비스에 매번 연결하기보다 monkeypatch나 테스트 대역으로 경계를 분리합니다. 단위 테스트에서는 내 코드의 판단 로직을 검증하고, 실제 연동 확인은 별도의 통합 테스트로 구분해야 실패 원인을 빠르게 찾을 수 있습니다.

반복 실행 가능한 테스트 자동화 구성

테스트는 로컬에서 한 번 성공하는 것으로 끝나지 않습니다. 의존성 파일에 pytest 버전 범위를 기록하고, 팀원이 같은 명령으로 실행할 수 있도록 pyproject.toml에 공통 옵션을 설정하세요. 이후 Git 저장소의 자동화 환경에서 코드가 변경될 때마다 python -m pytest를 실행하면 회귀 오류를 조기에 발견할 수 있습니다.

# pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
markers = [
    "slow: 실행 시간이 긴 테스트",
    "integration: 외부 시스템 연동 테스트",
]

빠른 단위 테스트와 느린 통합 테스트에는 marker를 붙여 실행 범위를 나눌 수 있습니다. 사용자 정의 marker를 설정 파일에 등록하지 않으면 오타가 있어도 놓치기 쉬우므로 등록 목록을 관리하세요. 자동화에서는 테스트가 하나도 수집되지 않았거나 실패했는데도 성공으로 취급하지 않도록 명령의 종료 코드를 그대로 반영해야 합니다.

pytest 실수 방지 체크리스트

  • pytest를 애플리케이션과 같은 가상환경에 설치했는지 확인합니다.
  • 파일과 함수 이름이 테스트 자동 수집 규칙을 따르는지 확인합니다.
  • 각 테스트는 다른 테스트의 실행 순서와 결과에 의존하지 않게 만듭니다.
  • 공통 준비 데이터는 Fixture로 분리하되 공유 범위를 필요 이상으로 넓히지 않습니다.
  • 정상값뿐 아니라 빈 값, 경계값, 잘못된 값도 매개변수화해 검사합니다.
  • 예외 테스트에서는 구체적인 예외 클래스와 필요한 메시지를 확인합니다.
  • 실제 API·메일·결제 전송이 단위 테스트 중 실행되지 않게 외부 경계를 대체합니다.
  • 실패 테스트를 삭제하거나 무조건 skip해 자동화 통과율만 맞추지 않습니다.
  • 개발 중 부분 실행 후 최종 단계에서 전체 테스트를 다시 실행합니다.

자주 묻는 질문

1. pytest는 파이썬에 기본 포함되어 있나요?

아닙니다. 사용하는 가상환경에서 python -m pip install -U pytest로 설치해야 합니다.

2. unittest와 pytest를 함께 사용할 수 있나요?

pytest는 기존 unittest 기반 테스트도 실행할 수 있습니다. 새 테스트부터 pytest 방식으로 점진적으로 바꿀 수 있습니다.

3. 테스트가 하나도 수집되지 않는 이유는 무엇인가요?

파일명이 test_*.py 또는 *_test.py인지, 함수명이 test_로 시작하는지, 올바른 폴더에서 실행했는지 확인하세요.

4. Fixture를 테스트 함수에서 어떻게 사용하나요?

Fixture 함수 이름을 테스트 함수의 매개변수로 적으면 pytest가 자동으로 실행하고 결과를 전달합니다.

5. conftest.py는 직접 import해야 하나요?

아닙니다. pytest가 해당 디렉터리 계층의 conftest.py를 찾아 Fixture와 훅을 불러옵니다.

6. Fixture scope는 무엇을 의미하나요?

Fixture가 새로 만들어지는 범위입니다. 기본 function 외에 class, module, package, session 범위가 있으며 상태 공유 여부를 고려해 선택해야 합니다.

7. 같은 테스트를 여러 값으로 실행하려면 어떻게 하나요?

@pytest.mark.parametrize에 매개변수 이름과 값 목록을 전달하면 각 값 묶음이 별도 사례로 실행됩니다.

8. 특정 테스트만 실행할 수 있나요?

파일 경로 뒤에 ::함수명을 붙이거나 -k 옵션으로 이름을 선택할 수 있습니다.

9. print 출력이 보이지 않는 이유는 무엇인가요?

pytest가 기본적으로 출력을 캡처하기 때문입니다. 디버깅 중에는 -s를 사용할 수 있지만 장기적으로는 assertion과 logging을 활용하는 편이 좋습니다.

10. 테스트 자동화는 언제 실행해야 하나요?

코드를 저장할 때의 빠른 로컬 실행, 저장소에 올리기 전 전체 실행, 변경 요청이나 배포 전 자동 실행을 조합하면 좋습니다.

공식 출처

공식 문서 확인일: 2026년 9월 9일. 설치된 pytest 버전에 따라 지원 기능과 옵션이 달라질 수 있으므로 실행 환경의 버전과 공식 문서를 함께 확인하세요.

본문 하단 광고 구역