|

pytest fixture는 언제 써야 할까: 테스트 준비 코드를 반복하지 않는 방법

pytest fixture 주제를 설명하는 대표 이미지
pytest fixture는 언제 써야 할까: 테스트 준비 코드를 반복하지 않는 방법

pytest fixture는 테스트를 실행하기 전에 필요한 준비 코드를 함수로 분리해 재사용하는 방법입니다. 테스트 함수가 fixture 이름을 인자로 받으면, pytest가 그 fixture를 먼저 실행하고 결과를 테스트에 넘겨줍니다.

중요한 점은 fixture가 단순한 중복 제거 도구만은 아니라는 것입니다. 각 테스트가 어떤 준비 상태에 의존하는지 함수 인자만 보고 드러나게 만드는 장치입니다.

pytest fixture 사용 기준 카드
pytest fixture는 반복 준비 코드와 테스트 독립성을 함께 다룬다

pytest fixture가 필요한 순간

pytest 공식 fixture 문서는 테스트 함수가 필요한 fixture를 인자로 요청하면 pytest가 같은 이름의 fixture를 찾아 실행한다고 설명합니다.

Python 프로젝트 설정 흐름은 pyproject.toml 글과 함께 보면 좋습니다. 테스트 도구 설정과 fixture 설계가 결국 같은 프로젝트 품질 관리 흐름에 있기 때문입니다.

fixture는 테스트마다 같은 객체를 만들거나, 같은 파일을 준비하거나, 같은 설정을 반복할 때 유용합니다. 예를 들어 사용자 객체를 만들고, 임시 저장소를 준비하고, 테스트용 설정값을 넘기는 코드가 여러 테스트에 흩어져 있다면 fixture로 분리할 수 있습니다.

import pytest

@pytest.fixture
def user():
    return {"id": 1, "name": "kim", "active": True}

def test_active_user_can_login(user):
    assert user["active"] is True

pytest 공식 문서는 테스트 함수가 필요한 fixture를 인자로 선언하면, pytest가 같은 이름의 fixture를 찾아 실행하고 그 결과를 넘긴다고 설명합니다.


fixture의 장점은 독립성이다

테스트 준비 코드를 그냥 전역 변수로 빼면 중복은 줄어들 수 있습니다. 하지만 테스트끼리 같은 객체를 공유하면서 한 테스트의 변경이 다른 테스트에 영향을 줄 수 있습니다. fixture는 기본적으로 테스트마다 새로 실행되므로 이런 위험을 줄이는 데 도움이 됩니다.

@pytest.fixture
def cart():
    return []

def test_add_book(cart):
    cart.append("book")
    assert cart == ["book"]

def test_empty_cart(cart):
    assert cart == []

두 테스트는 같은 cart fixture를 요청하지만, 같은 리스트 객체를 공유하지 않습니다. 이 차이가 테스트를 반복 실행해도 결과가 흔들리지 않게 해 줍니다.


scope는 성능보다 의미를 먼저 봐야 한다

pytest fixture에는 function, class, module, package, session 같은 scope를 줄 수 있습니다. scope가 넓어질수록 fixture는 덜 자주 실행됩니다. 그래서 DB 연결이나 무거운 리소스에는 도움이 될 수 있습니다.

하지만 scope를 넓히면 상태 공유 위험도 커집니다. 성능 때문에 무조건 session scope를 쓰기보다, 그 fixture가 테스트 사이에서 공유되어도 안전한지 먼저 확인해야 합니다.

  • 값이 바뀌는 객체라면 function scope가 안전하다
  • 읽기 전용 설정값은 module이나 session scope를 고려할 수 있다
  • 외부 리소스는 생성과 정리 비용을 함께 본다
  • 테스트 순서에 따라 결과가 달라지면 scope가 너무 넓을 가능성이 있다

autouse fixture는 조심해서 써야 한다

autouse=True를 쓰면 테스트 함수가 인자로 요청하지 않아도 fixture가 자동 실행됩니다. 공통 환경 설정에는 편하지만, 남용하면 테스트가 무엇에 의존하는지 코드에서 잘 보이지 않습니다.

@pytest.fixture(autouse=True)
def clean_env(monkeypatch):
    monkeypatch.delenv("API_TOKEN", raising=False)

위 예시처럼 모든 테스트에서 반드시 필요한 환경 정리라면 autouse가 자연스럽습니다. 반대로 특정 테스트 몇 개에만 필요한 데이터 준비라면 인자로 명시하는 편이 읽기 쉽습니다.


fixture와 parametrize는 역할이 다르다

fixture는 준비 상태를 만드는 도구이고, parametrize는 같은 테스트를 여러 입력으로 반복하는 도구입니다. 둘을 섞어 쓰면 테스트가 깔끔해지지만, 역할을 혼동하면 오히려 구조가 흐려집니다.

@pytest.mark.parametrize(
    "price, expected",
    [(1000, 1000), (1200, 1200), (0, 0)],
)
def test_display_price(price, expected):
    assert price == expected

입력값 조합을 늘리는 문제라면 parametrize가 맞고, 테스트를 실행하기 전에 필요한 객체나 환경을 만드는 문제라면 fixture가 맞습니다.


정리

pytest fixture는 테스트 코드를 짧게 만드는 기능보다, 테스트의 준비 상태를 명확하게 드러내는 기능에 더 가깝습니다. 반복되는 준비 코드가 있고, 각 테스트가 독립적으로 실행되어야 한다면 fixture를 쓰는 것이 좋습니다.

다만 scope와 autouse는 편리한 만큼 테스트 의존성을 숨길 수 있습니다. 처음에는 function scope와 명시적 인자 요청을 기본값으로 두고, 필요할 때만 넓히는 편이 안전합니다.

함께보면 좋은 글