pytest Fixture 스코프 문제 해결 가이드
pytest fixture의 스코프를 올바르게 설정하고 테스트 간 상태 공유 문제를 해결하는 방법을 알아봅니다.
pytest Fixture 스코프 문제 해결 가이드
pytest fixture는 테스트 코드에서 공통 리소스를 관리하는 강력한 도구입니다. 하지만 fixture 스코프를 잘못 설정하면 테스트 간 의존성 문제가 발생할 수 있습니다.
Environment
$ python --version
Python 3.11.5
$ pip show pytest
Name: pytest
Version: 7.4.3
$ pip show pytest-randomly
Name: pytest-randomly
Version: 3.15.0Problem: 테스트 간 상태 공유 문제
# conftest.py
import pytest
@pytest.fixture
def database():
# This creates a new connection for EACH test
db = create_connection()
yield db
db.close()
# test_user.py
def test_create_user(database):
user = database.create_user("joel")
assert user.name == "joel"
def test_get_user(database):
# Expects to find user from previous test, but fixture creates new connection
user = database.get_user("joel")
assert user is None # FAILS - database is empty!실행 결과:
$ pytest test_user.py -v
test_user.py::test_create_user PASSED
test_user.py::test_get_user FAILEDAnalysis: Fixture 스코프 이해
# pytest fixture scopes:
# function (default) - runs once per test function
# class - runs once per test class
# module - runs once per module
# session - runs once per test session
# Display fixture scope information
$ pytest --fixtures -v | grep -A 2 "database"
# Check fixture call count
import pytest
@pytest.fixture(scope="function")
def counted_fixture():
return object()
def test1(counted_fixture):
pass
def test2(counted_fixture):
pass
def test3(counted_fixture):
passSolution: Fixture 스코프 설정
function 스코프 (기본값)
# conftest.py
@pytest.fixture(scope="function")
def fresh_database():
"""Each test gets a fresh database"""
db = Database()
db.create_tables()
yield db
db.drop_tables()
db.close()
# 테스트 간 독립적
def test_user_creation(fresh_database):
fresh_database.create_user("joel")
# Fresh DB in next test
def test_user_query(fresh_database):
# DB is empty - no user from previous test
assert fresh_database.get_user_count() == 0class 스코프
@pytest.fixture(scope="class")
def shared_database():
"""All tests in class share same database"""
db = Database()
db.create_tables()
yield db
db.drop_tables()
class TestUser:
def test_create(self, shared_database):
shared_database.create_user("joel")
def test_get(self, shared_database):
# User exists from test_create
user = shared_database.get_user("joel")
assert user is not Nonesession 스코프
@pytest.fixture(scope="session")
def test_database():
"""All tests share same database"""
db = Database()
db.create_tables()
yield db
db.drop_tables()
# test_setup.py
def test_system_initialization(test_database):
test_database.initialize()###autouse fixture
@pytest.fixture(autouse=True, scope="session")
def setup_test_environment():
"""Automatically runs for all tests"""
os.environ["TESTING"] = "1"
yield
del os.environ["TESTING"]
@pytest.fixture(autouse=True)
def reset_state():
"""Runs before each test"""
yield
# Cleanup after each test
cache.clear()Advanced: 의존성 있는 Fixture
@pytest.fixture(scope="session")
def db_config():
return {"host": "localhost", "port": 5432}
@pytest.fixture(scope="session")
def database(db_config):
db = connect(db_config)
yield db
db.close()
@pytest.fixture(scope="function")
def db_session(database):
"""Transaction that rolls back after each test"""
transaction = database.begin()
yield database
transaction.rollback()
# 테스트에서 사용
def test_user(db_session):
db_session.execute("INSERT INTO users ...")
# Automatically rolled back after testFixture 간 의존성 관리
@pytest.fixture
def base_url():
return "http://localhost:8000"
@pytest.fixture
def api_client(base_url):
return APIClient(base_url)
@pytest.fixture
def authenticated_client(api_client):
api_client.login("admin", "password")
return api_client
# Usage
def test_api(authenticated_client):
response = authenticated_client.get("/api/data")
assert response.status_code == 200Troubleshooting
# 1. Fixture 재사용 확인
$ pytest --fixtures -v
# 2. Fixture 호출 횟수 확인
$ pytest -v --tb=short
# 3. Fixture 스코프 정보 출력
import pytest
pytest.fixture(autouse=True)(lambda: None) # Debug fixture
# 4. Fixture 정의 위치 확인
$ pytest --co -q
# 5. Fixture 에러 추적
$ pytest -v --setup-showLessons Learned
스코프 선택: 테스트 간 상태 공유가 필요 없으면
function스코프, 공유가 필요하면class또는session스코프를 사용하세요.테스트 독립성: 테스트는 서로 독립적이어야 합니다. 공유 상태는 의도치 않은 테스트 실패를 초래합니다.
autouse 주의: autouse fixture는 모든 테스트에 영향을 주므로 신중하게 사용하세요.
의존성 관리: Fixture 간 의존성이 복잡해지면, 각 fixture가 독립적으로 동작하도록 리팩토링하세요.
정리 로직: yield fixture의 teardown 부분에서 리소스를 정리하는 것을 잊지 마세요.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.