Pytest 픽스처 마스터하기: 고급 범위 매개변수화 및 종속성 관리
Lukas Schneider
DevOps Engineer · Leapcell

소개
테스팅은 소프트웨어 개발에서 애플리케이션의 신뢰성과 정확성을 보장하는 데 필수적인 부분입니다. 파이썬에서 사용 가능한 수많은 테스팅 프레임워크 중에서 pytest
는 유연성, 강력한 기능 및 사용 편의성으로 인해 두드러집니다. pytest
의 강력함의 초석은 픽스처 시스템에 있습니다. 간단한 테스트 사전 조건을 설정하는 것과 같은 기본적인 픽스처 사용은 간단하지만, pytest
픽스처의 잠재력을 최대한 활용하려면 고급 기능으로 깊이 들어가야 합니다. 픽스처 범위, 매개변수화 및 종속성 관리와 같은 미묘한 측면을 이해하면 테스트 효율성을 크게 향상시키고 중복을 줄이며 더 유지 관리 가능하고 강력한 테스트 스위트를 만들 수 있습니다. 이 기사에서는 이러한 고급 사용법을 자세히 살펴보고 pytest
숙달을 높이는 데 도움을 줄 것입니다.
핵심 개념
고급 패턴을 탐색하기 전에 후속 논의를 이해하는 데 중요한 pytest
픽스처와 관련된 몇 가지 핵심 개념을 간략하게 정의해 보겠습니다.
- 픽스처:
pytest
가 테스트(또는 테스트 세트)를 위해 필요한 리소스나 상태를 설정하기 전에 발견하고 실행하는@pytest.fixture
로 장식된 특수 함수입니다. 테스트가 인수로 받는 값을 반환할 수 있습니다. - 범위: 픽스처 함수가 실행되는 빈도를 결정합니다. 설정이 언제 발생하는지, 그리고 (있는 경우) 해제 로직이 언제 호출되는지를 정의합니다.
- 매개변수화: 다른 입력 매개변수로 동일한 테스트 함수 또는 픽스처를 여러 번 실행하는 프로세스입니다. 이는 반복적인 코드를 작성하지 않고도 다양한 시나리오를 테스트하는 데 매우 효과적입니다.
- 종속성 주입: 객체(이 경우 픽스처)가 자체적으로 생성하는 대신 외부 엔터티(
pytest
)에 의해 종속성이 제공되는 소프트웨어 설계 패턴입니다. 이는 느슨한 결합과 쉬운 테스팅을 촉진합니다.
고급 픽스처 관리
리소스 최적화를 위한 픽스처 범위 이해
픽스처 범위는 리소스와 실행 시간을 관리하는 데 중요한 개념입니다. pytest
는 여러 범위를 제공하며, 이는 픽스처가 얼마나 자주 설정되고 해제되는지에 영향을 미칩니다.
function
: 기본 범위입니다. 픽스처는 테스트 함수 호출 당 한 번 설정됩니다. 해제는 각 테스트 함수 후에 발생합니다. 테스트별, 격리된 리소스에 이상적입니다.class
: 픽스처는 테스트 클래스 당 한 번 설정됩니다. 해제는 클래스 내의 모든 테스트가 실행된 후에 발생합니다. 테스트 클래스의 메서드 간에 공유되는 리소스에 유용합니다.module
: 픽스처는 테스트 모듈 당 한 번 설정됩니다. 해제는 모듈의 모든 테스트가 실행된 후에 발생합니다. 파일의 모든 테스트에 필요한 리소스에 적합합니다.session
: 픽스처는pytest
세션 당 한 번 설정됩니다. 해제는 모든 모듈의 모든 테스트가 실행된 후에 발생합니다. 데이터베이스 연결과 같이 전역적으로 공유될 수 있는 비싼 리소스에 가장 적합합니다.
데이터베이스 연결을 포함하는 예시로 설명해 보겠습니다.
# conftest.py import pytest import sqlite3 @pytest.fixture(scope="session") def db_connection(): """ 전체 테스트 세션에 대한 데이터베이스 연결을 제공합니다. """ print("\nSetting up session DB connection...") conn = sqlite3.connect(":memory:") # 빠른 설정/해제를 위해 인메모리 DB 사용 cursor = conn.cursor() cursor.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)") conn.commit() yield conn print("Closing session DB connection...") conn.close() @pytest.fixture(scope="function") def user_repository(db_connection): """ 각 테스트 함수에 대한 사용자 리포지토리 인스턴스를 제공하며, 세션 범위의 DB 연결에 의존합니다. """ print("Setting up function user repository...") cursor = db_connection.cursor() # 테스트마다 테이블을 정리하여 격리를 보장합니다 cursor.execute("DELETE FROM users") db_connection.commit() class UserRepository: def __init__(self, conn): self.conn = conn def add_user(self, name): cursor = self.conn.cursor() cursor.execute("INSERT INTO users (name) VALUES (?)", (name,)) self.conn.commit() def get_all_users(self): cursor = self.conn.cursor() cursor.execute("SELECT name FROM users") return [row[0] for row in cursor.fetchall()] yield UserRepository(db_connection) print("Teardown function user repository...") # test_users.py def test_add_user(user_repository): user_repository.add_user("Alice") assert "Alice" in user_repository.get_all_users() def test_add_another_user(user_repository): # 이 테스트는 함수 범위 때문에 비어 있는 사용자 테이블에서 시작합니다 user_repository.add_user("Bob") assert "Bob" in user_repository.get_all_users()
이 테스트를 실행하면 다음과 같은 점을 관찰할 수 있습니다.
db_connection
의 설정 (Setting up session DB connection...
)은 시작 시 한 번 실행됩니다.user_repository
의 설정 (Setting up function user repository...
) 및 해제 (Teardown function user repository...
)는 각 테스트 함수 전후에 실행됩니다.db_connection
의 해제 (Closing session DB connection...
)는 세션이 끝날 때 한 번 실행됩니다.
이는 데이터베이스 연결과 같이 비싼 리소스에는 session
범위가 어떻게 사용되고, user_repository
상태를 각 테스트에 대해 재설정하여 테스트 격리를 보장하는 데는 function
범위가 어떻게 사용되는지를 보여줍니다.
다양한 테스트 조건에 대한 픽스처 매개변수화
매개변수화를 사용하면 동일한 픽스처 설정을 다양한 입력으로 여러 번 실행할 수 있으며, 이는 다양한 구성이나 데이터 시나리오를 테스트하는 데 매우 유용합니다. 이는 pytest.mark.parametrize
를 사용하거나 @pytest.fixture
데코레이터에 params
를 전달하여 달성할 수 있습니다.
@pytest.fixture
와 함께 params
사용:
# conftest.py import pytest @pytest.fixture(params=["chrome", "firefox", "edge"], scope="function") def browser(request): """ 테스트를 위해 다른 브라우저 인스턴스를 제공합니다. """ browser_name = request.param print(f"\nSetting up {browser_name} browser...") # 브라우저 설정 시뮬레이션 yield f"WebDriver for {browser_name}" print(f"Closing {browser_name} browser...") # test_browsers.py def test_home_page_loads(browser): """ 다른 브라우저에 대해 홈 페이지가 올바르게 로드되는지 테스트합니다. """ print(f"Testing with: {browser}") assert "WebDriver" in browser # 기본 확인 assert "page loaded" == "page loaded" # 실제 확인 시뮬레이션
pytest test_browsers.py
를 실행하면 test_home_page_loads
함수가 각각의 브라우저 인스턴스를 제공하는 browser
픽스처와 함께 세 번 (chrome
, firefox
, edge
) 실행됩니다. pytest
에서 자동으로 제공되는 request
픽스처는 request.param
을 통해 현재 매개변수 값에 액세스할 수 있습니다.
이를 통해 각 브라우저 유형에 대해 별도의 테스트를 작성하는 반복적인 코드 작성이 필요 없어집니다.
구조화된 테스팅을 위한 픽스처 종속성 관리
픽스처는 다른 픽스처에 종속될 수 있습니다. pytest
는 픽스처 및 테스트의 함수 서명을 검사하여 이러한 종속성 주입을 자동으로 처리합니다. 픽스처 A
가 픽스처 B
를 필요로 하는 경우, A
에 B
를 인수로 선언하기만 하면 됩니다. 그러면 pytest
는 B
가 A
전에 설정되도록 보장합니다. 이는 명확하고 명시적인 종속성 그래프를 생성하여 테스트 설정을 매우 모듈화되고 유지 관리 가능하게 만듭니다.
범위 섹션의 user_repository
예제를 고려해 보세요. user_repository
픽스처는 db_connection
에 종속됩니다.
@pytest.fixture(scope="function") def user_repository(db_connection): # 'db_connection'은 종속성입니다 # ... 설정 로직 ... yield UserRepository(db_connection) # ... 해제 로직 ...
여기서 pytest
는 먼저 db_connection
을 설정합니다(세션 범위이므로 한 번만 발생합니다). 그런 다음 user_repository
가 테스트에 대해 호출될 때 결과 연결 객체를 user_repository
에 전달합니다. 이를 통해 user_repository
는 항상 유효하고 초기화된 데이터베이스 연결에서 작동하도록 보장합니다.
이 종속성 주입 패턴은 복잡한 테스팅 환경을 구축하는 데 강력합니다. feature_service
는 database_client
에 종속될 수 있고, 이는 다시 database_credentials
에 종속되는 종속성 체인을 가질 수 있습니다. Pytest는 전체 해결 순서를 처리하고, 종속성을 올바른 순서로 설정하고 적절하게 해제합니다.
예를 들어, 더 복잡한 시나리오는 다음과 같습니다.
# conftest.py @pytest.fixture(scope="session") def config_loader(): """애플리케이션 구성을 로드합니다.""" print("Loading configuration...") class Config: DB_URL = "sqlite:///:memory:" API_KEY = "dummy_api_key" yield Config print("Configuration teardown...") @pytest.fixture(scope="session") def api_client(config_loader): """API 클라이언트 인스턴스를 제공합니다.""" print("Setting up API client...") class APIClient: def __init__(self, api_key): self.api_key = api_key def get_data(self): return f"Data from API with key: {self.api_key}" yield APIClient(config_loader.API_KEY) print("API client teardown...") # test_integration.py def test_api_integration(api_client): assert "dummy_api_key" in api_client.get_data()
여기서 api_client
는 명시적으로 config_loader
에 종속됩니다. Pytest는 config_loader
가 먼저 실행되도록 하고, 해당 Config
객체를 api_client
에 전달하도록 보장합니다. 이 패턴은 모듈화를 촉진합니다. 인터페이스가 일관되는 한 api_client
에 영향을 주지 않고 config_loader
를 다른 구현으로 바꿀 수 있습니다.
결론
pytest
픽스처, 특히 범위 관리, 매개변수화 및 종속성 주입의 고급 기능을 마스터하는 것은 효율적이고 유지 관리 가능하며 강력한 테스트 스위트를 작성하는 데 중요합니다. 픽스처 범위를 전략적으로 선택함으로써 리소스 사용량과 실행 시간을 최적화할 수 있습니다. 매개변수화는 최소한의 코드 중복으로 수많은 시나리오를 테스트할 수 있도록 합니다. 마지막으로, 픽스처 컴포지션을 통한 명시적인 종속성 관리는 깨끗하고 모듈화되며 이해하기 쉬운 테스트 설정을 이끌어냅니다. 이러한 고급 기법을 채택하면 pytest
경험이 변화하여 더 높은 품질의 소프트웨어를 자신 있게 구축할 수 있습니다.