
파이썬 테스트 프레임워크인 pytest에서 fixture는 테스트의 재사용성과 모듈화를 가능하게 하는 핵심 요소입니다. 하지만 많은 개발자가 fixture의 scope(범위) 설정을 간과하여 전체 테스트 실행 속도가 기하급수적으로 느려지거나, 테스트 간 데이터 오염으로 인한 'Flaky Test(실행할 때마다 결과가 달라지는 테스트)' 문제를 겪곤 합니다. 본 포스팅에서는 실무 프로젝트의 규모가 커질수록 중요해지는 fixture scope의 전략적 배치 노하우를 다룹니다. 특히 function부터 session까지 각 범위가 가지는 메모리 및 성능상의 결정적 차이를 분석하고, 실무에서 즉시 적용 가능한 7가지 최적화 예제를 통해 효율적인 테스트 환경 구축법을 제시합니다.
1. Pytest Fixture Scope의 종류와 결정적 차이
fixture의 scope는 해당 객체가 얼마나 자주 생성되고 파괴되는지를 결정합니다. 이를 잘못 설정하면 DB 연결을 수천 번 반복하거나, 반대로 이전 테스트의 쓰레기 값이 다음 테스트에 영향을 미치는 대참사가 발생할 수 있습니다.
| Scope 명칭 | 생성 및 파괴 주기 | 주요 권장 용도 | 성능/격리 차이 |
|---|---|---|---|
| function | 각 테스트 함수마다 실행 | 상태 변화가 잦은 Mock 데이터, 단위 테스트 | 격리성 최고 / 속도 가장 느림 |
| class | 테스트 클래스당 1회 실행 | 동일 클래스 내 메서드 간 공유 자원 | 중간 수준의 효율성 |
| module | 파이썬 파일(.py)당 1회 실행 | 해당 모듈 전용 무거운 설정 로드 | 높은 효율 / 모듈 간 격리 |
| package | 패키지(디렉토리)당 1회 실행 | 서브 패키지 단위의 공통 환경 | 대규모 프로젝트 유용 |
| session | 전체 테스트 세션 중 단 1회 | DB 커넥션, API 토큰, 글로벌 설정 | 속도 최고 / 격리 주의 필요 |
2. 실무 개발자를 위한 Fixture Scope 최적화 노하우 5계명
- 상태를 변경(Side-effect)하는 자원은 무조건 function: 데이터베이스에 쓰기 작업을 하거나 전역 변수를 수정한다면
functionscope를 사용하여 매번 초기화해야 합니다. - 읽기 전용 무거운 자원은 session: 머신러닝 모델 로드나 외부 설정 파일 로드는 전체 테스트에서 딱 한 번만 수행하도록
session으로 설정하세요. - 테스트 병렬화(pytest-xdist) 고려:
sessionscope fixture는 병렬 실행 시 프로세스마다 별도로 생성될 수 있음을 인지하고 설계해야 합니다. - autouse=True 사용 지양: 모든 테스트에 강제로 적용되는 autouse는 scope가 넓을수록 의도치 않은 오버헤드를 발생시킵니다.
- yield를 이용한 정리(Teardown) 필수: 특히
session이나module범위에서는 사용 후 반드시 자원을 해제(Connection Close 등)해야 메모리 누수를 막을 수 있습니다.
3. 실무에 바로 적용하는 고도화된 7가지 Sample Examples
단순한 예제를 넘어, 실제 현업 백엔드 및 앱 개발 환경에서 빈번하게 마주치는 문제들을 해결하는 코드들입니다.
Example 1: 데이터베이스 커넥션 풀링 (session scope)
전체 테스트 기간 동안 한 번만 연결하고 모든 테스트가 공유하는 방식입니다.
import pytest
import psycopg2
@pytest.fixture(scope="session")
def db_conn():
# 전체 테스트 시작 시 1회 실행
conn = psycopg2.connect("dbname=test user=postgres")
yield conn
# 전체 테스트 종료 시 1회 실행
conn.close()
Example 2: 테스트별 DB 트랜잭션 롤백 (function scope)
session 커넥션을 사용하되, 각 테스트가 끝날 때마다 데이터를 롤백하여 격리성을 확보하는 해결 방법입니다.
@pytest.fixture(scope="function")
def db_session(db_conn):
cursor = db_conn.cursor()
yield cursor
# 테스트가 끝나면 실제 반영하지 않고 롤백하여 데이터 오염 방지
db_conn.rollback()
Example 3: 대용량 머신러닝 모델 로딩 (module scope)
특정 테스트 파일 내에서만 사용하는 무거운 객체를 효율적으로 관리합니다.
import torch
@pytest.fixture(scope="module")
def heavy_model():
model = torch.load("path/to/large_model.pt")
model.eval()
return model
Example 4: 외부 API 인증 토큰 관리 (session scope)
테스트마다 로그인 API를 호출하는 오버헤드를 제거합니다.
import requests
@pytest.fixture(scope="session")
def auth_token():
response = requests.post("https://api.test.com/login", data={"user": "test"})
return response.json()["token"]
Example 5: 임시 작업 디렉토리 생성 (class scope)
동일한 테스트 클래스에 속한 여러 메서드가 하나의 폴더를 공유하여 작업할 때 유용합니다.
import tempfile
import shutil
@pytest.fixture(scope="class")
def tmp_dir_factory(request):
tmp_dir = tempfile.mkdtemp()
# 클래스 속성으로 디렉토리 경로 주입
request.cls.tmp_dir = tmp_dir
yield tmp_dir
shutil.rmtree(tmp_dir)
Example 6: 동적 Scope 결정 (Dynamic Scoping)
환경 변수나 옵션에 따라 scope를 런타임에 변경하는 고급 테크닉입니다.
def determine_scope(fixture_name, config):
if config.getoption("--fast-mode", default=False):
return "session"
return "function"
@pytest.fixture(scope=determine_scope)
def dynamic_fixture():
return "This scope changes based on CLI flags"
Example 7: 병렬 테스트를 위한 락 파일(Lock File) 관리 (session scope)
pytest-xdist 사용 시 여러 프로세스가 동시에 자원에 접근하는 것을 방지합니다.
import os
from filelock import FileLock
@pytest.fixture(scope="session")
def shared_resource(tmp_path_factory):
root_tmp_dir = tmp_path_factory.getbasetemp().parent
fn = root_tmp_dir / "data.lock"
with FileLock(str(fn)):
# 단 한 번만 수행해야 하는 초기화 로직
print("Initializing shared global resource...")
yield "resource_data"
4. 결론: 성능과 안정성의 트레이드오프 해결하기
pytest fixture의 scope를 결정하는 것은 결국 '성능(속도)'과 '신뢰성(격리)' 사이의 균형을 잡는 일입니다. 모든 fixture를 session으로 두면 테스트는 번개처럼 빠르겠지만, 하나라도 데이터를 수정하는 순간 나머지 수백 개의 테스트 결과는 믿을 수 없게 됩니다. 가장 좋은 전략은 기본을 function으로 설정하되, 병목이 발생하는 읽기 전용 자원부터 점진적으로 범위를 넓혀나가는 것입니다. 본 가이드에서 제시한 7가지 사례를 프로젝트에 대입해본다면, 테스트 스위트의 실행 시간을 절반 이하로 줄이면서도 견고한 테스트 환경을 유지할 수 있을 것입니다.
5. 내용의 출처 및 참고 문헌
- pytest 공식 문서: Fixtures: explicit, modular, scalable
- Python Testing with pytest (Brian Okken) - Fixture Scopes section
- Full Stack Python: Unit Testing Python Code with pytest
'Artificial Intelligence > 60. Python' 카테고리의 다른 글
| [PYTHON] 전략(Strategy) 패턴을 파이썬의 일급 객체 특성으로 구현하는 3가지 방법과 클래스와의 결정적 차이 7가지 (0) | 2026.04.01 |
|---|---|
| [PYTHON] 왜 AI 개발에 Python이 가장 많이 쓰이나요? 5가지 이유와 타 언어와의 결정적 차이 해결 사례 (0) | 2026.04.01 |
| [PYTHON] Anaconda와 일반 Python의 5가지 결정적 차이 및 환경 충돌 해결 방법 (0) | 2026.04.01 |
| [PYTHON] 가상환경(venv, conda)을 왜 3가지 이유로 꼭 써야 하나요? 충돌 해결 방법 7가지 (0) | 2026.04.01 |
| [PYTHON] Jupyter vs PyCharm/VS Code 결정적 차이 3가지와 상황별 해결 방법 7가지 (0) | 2026.04.01 |