Selenium + Python 테스트 자동화 추천 라이브러리 6선|QA 엔지니어 실무 필수 도구

테스트 자동화

pytest 픽스처(fixture)는 테스트 코드의 중복을 줄이고, 셋업과 정리(teardown)를 한 곳에서 관리할 수 있는 pytest의 핵심 기능입니다. yield·scope·conftest.py를 제대로 활용하면 유지보수하기 쉬운 테스트를 작성할 수 있습니다.

📌 이 글의 대상 독자

✅ pytest를 막 시작한 분
✅ 픽스처 작성에 자신이 없는 분
✅ yield·scope·conftest.py의 차이를 정리하고 싶은 분

📖 이 글에서 배울 수 있는 내용

✔ 픽스처의 기본 문법과 동작 원리
✔ yield를 활용한 정리(teardown) 패턴 (실무에서 가장 많이 쓰는 방법)
✔ scope 선택 방법과 병렬 실행 시 주의사항
✔ conftest.py의 올바른 배치 위치
✔ autouse와 내장 픽스처(tmp_path 등) 활용법
✔ 실무에서 자주 겪는 문제와 해결 방법

필자는 QA 엔지니어로 15년 이상 Selenium·Playwright·pytest를 활용한 테스트 자동화 업무를 해왔습니다.
이 글은 교과서적인 설명에 그치지 않고, 실제 현장에서 자주 보이는 코드 문제점을 함께 다룹니다.

✅ 이 글의 핵심 정리

  • 픽스처는 yield를 사용해 셋업과 teardown을 하나로 묶는다
  • scope로 비용이 큰 처리를 재사용해 테스트 속도를 높인다
  • 공통 픽스처는 conftest.py에 두어 팀 전체가 활용한다

pytest 픽스처(fixture)란?

pytest 픽스처란 테스트 실행 전후에 필요한 셋업·정리·공유 데이터를 한 곳에서 관리하는 구조입니다.
@pytest.fixture 데코레이터를 붙인 함수를 정의하고, 테스트 함수의 인수에 같은 이름으로 받으면 pytest가 자동으로 주입해 줍니다.

JUnit의 @BeforeEach / @AfterEach와 유사한 개념이지만, pytest 픽스처는 유연한 스코프 제어·중첩·파라미터화가 가능해 더욱 강력합니다.

기본 문법과 간단한 사용 예

먼저 최소한의 구성부터 살펴보겠습니다.

import pytest

# ① 픽스처 정의
@pytest.fixture
def sample_data() -> dict:
    return {"user": "지민", "age": 30}

# ② 테스트 함수의 인수명 = 픽스처명 (자동 주입)
def test_user_name(sample_data: dict) -> None:
    assert sample_data["user"] == "지민"

def test_user_age(sample_data: dict) -> None:
    assert sample_data["age"] == 30

sample_data라는 인수명과 픽스처명이 일치하기만 하면 pytest가 자동으로 전달해 줍니다.
import는 필요 없으며, 여러 테스트 함수에서 같은 픽스처를 재사용할 수 있습니다.

추천 픽스처 패턴 5가지

1. yield로 셋업과 teardown을 하나로 묶기

실무에서 가장 많이 쓰는 패턴입니다. yield 앞이 셋업, 뒤가 teardown이 됩니다.
테스트가 성공하든 실패하든 teardown 코드는 반드시 실행됩니다.

import pytest
import requests
import logging

logger = logging.getLogger(__name__)

@pytest.fixture
def api_session() -> requests.Session:
    # --- 셋업 ---
    session = requests.Session()
    session.headers.update({"Content-Type": "application/json"})
    logger.info("세션 열림")

    yield session  # ← 테스트 함수에 전달

    # --- teardown (테스트 성공·실패 관계없이 실행) ---
    session.close()
    logger.info("세션 닫힘")


def test_get_users(api_session: requests.Session) -> None:
    response = api_session.get("https://example.com/api/users")  # 샘플 URL
    assert response.status_code == 200

return으로도 teardown을 구현하는 방법은 있지만, pytest에서는 yield를 사용하는 것이 teardown을 더 자연스럽게 작성할 수 있습니다.
DB 연결·HTTP 세션·브라우저 드라이버 등 닫아야 하는 리소스에는 실무에서 yield 패턴이 널리 쓰입니다.

2. scope로 비용이 큰 처리 재사용하기

기본적으로는 테스트마다 픽스처가 생성·소멸됩니다. scope를 지정하면 수명을 늘려 처리를 재사용할 수 있습니다.

scope수명주요 용도
function (기본값)테스트 1건마다가벼운 초기화·독립성이 필요한 데이터
class클래스 내클래스 단위 공통 데이터
module파일 내DB 연결·무거운 목 서버
session전체 테스트 실행브라우저 실행·인증 토큰 취득
import pytest
import requests

# 로그인 API 호출을 전체 테스트 실행에서 1번만 수행
@pytest.fixture(scope="session")
def auth_token() -> str:
    response = requests.post(
        "https://example.com/api/auth/login",  # 샘플 URL
        json={"username": "testuser", "password": "secret"},
        timeout=(3, 10),
    )
    response.raise_for_status()
    data = response.json()
    assert "token" in data
    return data["token"]


def test_get_profile(auth_token: str) -> None:
    headers = {"Authorization": f"Bearer {auth_token}"}
    res = requests.get(
        "https://example.com/api/profile",  # 샘플 URL
        headers=headers,
        timeout=(3, 10),
    )
    assert res.status_code == 200
⚠️ 주의: session 스코프 픽스처에서 상태를 변경하지 말 것
scope="session" 자체는 문제없지만, 여러 테스트가 공유하는 픽스처 안에서 상태를 변경하는 처리를 수행하면 pytest-xdist 등으로 병렬 실행할 때 테스트 간 간섭이 발생할 수 있습니다.
세션 스코프 픽스처는 읽기 전용 데이터·연결 객체에만 사용하는 것이 안전합니다.

3. conftest.py로 픽스처 공유하기

conftest.py에 작성한 픽스처는 같은 디렉터리 이하의 모든 테스트에서 import 없이 사용할 수 있습니다.
팀 전체가 공유하는 공통 픽스처를 두는 장소로 활용됩니다.

# 디렉터리 구성
tests/
├── conftest.py          ← 전체 테스트에서 사용할 수 있는 공통 픽스처
├── test_auth.py
└── api/
    ├── conftest.py      ← api/ 디렉터리 안에서만 사용 가능한 픽스처
    └── test_users.py
# tests/conftest.py
import pytest

@pytest.fixture(scope="session")
def base_url() -> str:
    return "https://example.com"  # 샘플 URL (실제로는 환경 변수 등으로 관리)

@pytest.fixture
def headers(auth_token: str) -> dict:
    return {"Authorization": f"Bearer {auth_token}"}
# tests/test_auth.py
# import 불필요 — conftest.py의 픽스처는 자동으로 인식됨
def test_base_url(base_url: str) -> None:
    assert "example" in base_url

conftest.py는 여러 계층에 배치할 수 있어 “프로젝트 공통 픽스처”와 “특정 모듈 전용 픽스처”를 깔끔하게 분리할 수 있습니다.

4. params로 데이터 기반 테스트 구현하기

params를 사용하면 픽스처가 반환하는 값을 여러 패턴으로 전환해 테스트를 반복 실행할 수 있습니다.

import pytest
import requests

INVALID_PAYLOADS = [
    pytest.param({"username": "", "password": "pass"}, id="empty_username"),
    pytest.param({"username": "user", "password": ""}, id="empty_password"),
    pytest.param({}, id="empty_body"),
]

# request는 pytest가 자동으로 전달하는 특수 인수
# (타입 힌트를 쓰려면 pytest.FixtureRequest)
@pytest.fixture(params=INVALID_PAYLOADS)
def invalid_login_payload(request) -> dict:
    return request.param


def test_login_validation(invalid_login_payload: dict) -> None:
    response = requests.post(
        "https://example.com/api/auth/login",  # 샘플 URL
        json=invalid_login_payload,
        timeout=(3, 10),
    )
    # 잘못된 요청은 모두 400계 응답을 반환해야 함
    assert response.status_code in (400, 422)

pytest.param(..., id="...")으로 각 패턴에 이름을 붙이면 테스트 실패 시 로그가 읽기 쉬워집니다.

fixture params vs. @pytest.mark.parametrize 비교

비교 항목fixture params@pytest.mark.parametrize
여러 테스트 함수에 같은 패턴 적용△ (함수마다 별도 기재 필요)
단일 테스트 함수의 단순 입력 패턴
셋업 처리도 함께 파라미터화
초보자 가독성

5. 픽스처를 픽스처에 주입해 역할 분리하기

픽스처의 인수에 다른 픽스처를 받을 수 있습니다. “연결 정보”·”인증”·”클라이언트 생성”을 각각 별도 픽스처로 분리하면 역할이 명확해집니다.

⚠️ 주입 방향에 주의
픽스처를 중첩할 때는 스코프가 넓은 픽스처를 좁은 픽스처에 주입하는 방향으로 설계해야 합니다. 역방향은 동작하지 않습니다.
session → function  ✅ (넓음 → 좁음: OK)
function → session  ❌ (좁음 → 넓음: ScopeMismatch 에러)
import pytest
import requests

# 1. 베이스 URL (session 스코프)
@pytest.fixture(scope="session")
def base_url() -> str:
    return "https://example.com/api"  # 샘플 URL (실제로는 환경 변수로 관리)


# 2. 인증 토큰 (session 스코프 · base_url 주입)
@pytest.fixture(scope="session")
def auth_token(base_url: str) -> str:
    response = requests.post(
        f"{base_url}/auth/login",
        json={"username": "testuser", "password": "secret"},
        timeout=(3, 10),
    )
    response.raise_for_status()
    data = response.json()
    assert "token" in data
    return data["token"]


# 3. 인증된 세션 (function 스코프 · auth_token 주입)
@pytest.fixture
def authed_session(base_url: str, auth_token: str) -> requests.Session:
    session = requests.Session()
    session.headers.update({
        "Authorization": f"Bearer {auth_token}",
        "Content-Type": "application/json",
    })
    yield session
    session.close()


# 테스트는 authed_session만 받으면 됨
def test_get_users(authed_session: requests.Session) -> None:
    res = authed_session.get(
        "https://example.com/api/users",  # 샘플 URL
        timeout=(3, 10),
    )
    assert res.status_code == 200

이 구성에서는 base_url만 바꾸면 환경 전환이 완료됩니다.
테스트 함수는 authed_session만 알면 되므로 가독성도 높아집니다.

6. autouse로 모든 테스트에 자동 적용하기 (응용)

autouse=True를 지정하면 인수로 선언하지 않아도 모든 테스트에 픽스처가 자동으로 적용됩니다.
로그 기록·타임존 고정·테스트 간 DB 초기화 등에 활용됩니다.

import pytest
import logging

logger = logging.getLogger(__name__)

# 모든 테스트의 시작·종료를 자동으로 로그에 기록
@pytest.fixture(autouse=True)
def log_test_name(request: pytest.FixtureRequest) -> None:
    logger.info(f"▶ 시작: {request.node.name}")
    yield
    logger.info(f"■ 종료: {request.node.name}")
⚠️ autouse 남용 주의
autouse=True는 편리하지만, 지나치게 사용하면 테스트가 어떤 픽스처에 의존하는지 파악하기 어려워져 디버깅이 힘들어질 수 있습니다. 팀에서 공통 인식이 있는 “모든 테스트에서 반드시 필요한 처리“에 한정해 사용하는 것이 실무에서 안전합니다.

알아두면 유용한 내장 픽스처

pytest에는 미리 제공되는 내장 픽스처가 있습니다. 직접 구현하지 않아도 바로 사용할 수 있어 편리합니다.

픽스처명용도대표적인 사용 예
tmp_path테스트용 임시 디렉터리파일 읽기·쓰기 테스트
monkeypatch환경 변수·속성 임시 교체외부 API나 설정값 목(mock)
caplog로그 출력 캡처logging 출력 내용을 assert로 검증
capsys표준 출력·표준 에러 캡처print() 출력 내용을 검증할 때
from pathlib import Path
import pytest

# tmp_path: 테스트마다 자동으로 임시 디렉터리가 생성됨
def test_write_csv(tmp_path: Path) -> None:
    output_file = tmp_path / "result.csv"
    output_file.write_text("id,name\n1,지민\n")
    assert output_file.read_text().startswith("id,name")

# monkeypatch: 환경 변수를 일시적으로 교체하고 테스트 후 원래대로 복원
def test_api_base_url(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setenv("API_BASE_URL", "https://staging.example.com")
    import os
    assert os.environ["API_BASE_URL"] == "https://staging.example.com"

실무에서 자주 겪는 함정 7가지

⚠️ pytest 픽스처에서 주의해야 할 포인트

  1. 테스트 함수명에 test_ 접두사가 없음
    pytest는 기본적으로 test_로 시작하는 함수만 수집합니다. check_login()처럼 작성하면 실행되지 않습니다.
  2. conftest.py의 위치가 잘못됨
    conftest.py는 테스트 파일과 같은 디렉터리 또는 상위 디렉터리에 있어야 합니다. 다른 위치에 두면 “fixture not found” 에러가 발생합니다.
  3. scope=”session” 픽스처에서 상태를 변경함
    세션 스코프에서 공유되는 픽스처 안에서 상태를 변경하면, pytest-xdist 등으로 병렬 실행할 때 테스트 간 간섭이 발생할 수 있습니다. 읽기 전용 데이터나 연결 객체에만 사용하세요.
  4. 커스텀 마크를 pytest.ini(또는 pyproject.toml)에 등록하지 않음
    @pytest.mark.smoke 등을 사용하는 경우, 설정 파일 등록 없이는 PytestUnknownMarkWarning이 발생합니다.
  5. print() 출력이 보이지 않음
    pytest는 기본적으로 표준 출력을 캡처합니다. 확인하려면 pytest -s를 붙이거나, logging 모듈을 사용하세요.
  6. assert 앞에서 예외가 발생하면 FAILED가 아닌 ERROR로 표시됨
    픽스처나 assert 전에 에러가 발생하면 테스트 결과가 FAILED가 아닌 ERROR로 표시됩니다. 메시지를 잘 읽고 트레이스백을 확인하세요.
  7. 루트 디렉터리가 아닌 곳에서 pytest를 실행해 ModuleNotFoundError 발생
    tests/ 디렉터리 안에서 pytest를 실행하면 프로젝트 루트가 Python 경로에 포함되지 않아 import 에러가 날 수 있습니다. pytest.ini가 있는 루트에서 실행하는 습관을 들이세요.

자주 묻는 질문 (FAQ)

Q. 픽스처와 setup_method / setup_function의 차이는?

setup_method(클래스 내)와 setup_function(모듈 내)은 xUnit 스타일의 초기화 메서드입니다. pytest 픽스처는 인수 주입·스코프 제어·파라미터화가 가능해 기능이 더 풍부합니다. 새로운 프로젝트에서는 픽스처 스타일이 널리 사용됩니다. 단, 기존의 xUnit 스타일 코드를 당장 바꿀 필요는 없으며 혼용도 가능합니다.

Q. 픽스처의 실행 순서는 어떻게 결정되나요?

pytest는 테스트 함수의 인수에 선언된 픽스처의 의존 관계 그래프를 분석해, 의존되는 쪽부터 실행합니다. 같은 스코프에서 의존 관계가 없는 픽스처 간의 순서는 보장되지 않습니다. 실행 순서를 명시하고 싶다면 픽스처를 다른 픽스처에 주입해 의존 관계를 만들거나, pytest-ordering 등의 플러그인을 검토하세요.

Q. autouse=True를 사용해야 할까요?

“모든 테스트에서 반드시 필요한 처리”(로그 기록·DB 리셋·타임존 고정 등)에는 유효합니다. 반면 남용하면 테스트가 어떤 픽스처에 의존하는지 파악하기 어려워져 디버깅이 힘들어질 수 있습니다. 원칙적으로 테스트 함수의 인수에 명시적으로 작성하는 스타일을 기본으로 하고, autouse는 팀에서 공통 인식이 있는 경우에만 한정하는 것이 안전합니다.

Q. fixture params와 @pytest.mark.parametrize는 어떻게 구분하나요?

@pytest.mark.parametrize는 하나의 테스트 함수에 여러 패턴을 적용하는 단순한 경우에 적합합니다. 픽스처의 params여러 테스트 함수에 같은 패턴을 재사용하고 싶을 때나, 셋업 처리까지 함께 파라미터화하고 싶을 때 편리합니다. 위의 비교표도 참고하세요.

Q. yield 뒤의 teardown 코드는 테스트 실패 시에도 실행되나요?

실행됩니다. yield를 사용한 픽스처는 try/finally와 동등한 동작을 하므로, 테스트가 FAILED가 되더라도 teardown 코드는 반드시 실행됩니다. 단, teardown 코드 자체에서 예외가 발생하면 경고가 출력되므로, 필요에 따라 try/except를 추가하세요.

Q. conftest.py는 몇 개까지 만들 수 있나요?

개수 제한은 없으며 디렉터리마다 배치할 수 있습니다. “프로젝트 공통”·”모듈 공통”·”테스트 종류별 공통”처럼 스코프를 의식해 분리하면 픽스처를 파악하기 쉬워집니다.

정리

핵심 포인트내용
yield로 teardown셋업과 teardown을 하나의 픽스처에 작성. 실무에서 return보다 널리 사용됨
scope로 재사용무거운 처리는 session/module로 확장해 테스트 고속화. 상태 변경에 주의
conftest.py로 공유import 없이 픽스처 재사용 가능. 배치 위치(디렉터리)에 주의
params로 데이터 기반여러 테스트 함수에 같은 패턴을 적용할 때 편리
픽스처 주입역할을 분리해 중첩하면 가독성·유지보수성 향상
autouse=True전체 테스트 자동 적용. 편리하지만 남용하면 디버깅이 어려워짐
내장 픽스처tmp_path / monkeypatch / caplog 등 구현 없이 사용 가능

pytest 픽스처를 활용하면 테스트 코드 중복이 줄고·teardown 누락이 없어지며·전체 테스트 속도도 높아집니다.
처음에는 yield 패턴과 conftest.py 사용법만 익혀두면 일상적인 테스트 자동화에 충분히 대응할 수 있습니다.

먼저 기존 테스트의 셋업 처리 하나를 픽스처로 분리해 보는 것부터 시작해 보세요.

제목과 URL을 복사했습니다