Pytest 픽스쳐

완료됨

비품은 테스트에 대해 정의하고 신뢰할 수 있는 컨텍스트를 제공합니다. 사용자 지정 픽스처는 @pytest.fixture 데코레이터를 사용한 Python 함수입니다. 테스트 데이터, 환경 변수, 데이터베이스 연결 또는 테스트가 실행되기 전에 필요한 파일과 같은 사전 조건을 정렬하는 데 사용합니다. 픽스처는 테스트나 픽스처 스코프가 끝난 뒤 리소스를 정리하는 종료 단계를 정의할 수도 있습니다.

테스트는 픽스처와 동일한 이름의 인수를 선언하여 픽스처를 요청합니다. Pytest는 해당 픽스처를 찾아 실행한 뒤, 반환되거나 산출된 값을 테스트 함수로 전달합니다. 비품 수명 주기에 대한 자세한 내용은 픽스트 설명서를 참조 하세요.

고정 장치는 설정 과정이나 정리 작업이 재사용될 때, 복잡할 때, 또는 이름이 지정될 때 더욱 유용합니다. 간단한 일회성 값의 경우 설치 코드를 테스트에 직접 유지하는 것이 더 쉽게 읽을 수 있습니다.

pytest 설비의 주요 특징은 다음과 같습니다.

  • 범위 제어: Fixtures는 매개 변수를 scope 사용하여 pytest가 비품 값을 만들고 삭제하는 빈도를 제어할 수 있습니다. 범위가 넓을수록 성능이 향상될 수 있지만 상태를 더 오랫동안 공유합니다.
  • 설정 및 해체: Fixture는 정리를 위해 yield을 사용할 수 있습니다. 고급 픽스처는 이 단원의 뒷부분에서 소개될 내장 request 픽스처를 주입받아 요청한 테스트를 검사하거나, request.addfinalizer() 를 호출하여 정리 콜백을 등록할 수도 있습니다.
  • 종속성 주입: 테스트와 픽스쳐는 종속성을 명시적으로 하기 위해 의존성을 인수로 요청합니다.
  • 재사용성 및 모듈성: 비품을 한 번 정의하고 테스트 함수, 모듈, 디렉터리 또는 프로젝트에서 다시 사용할 수 있습니다.
  • 자동 활성화: @pytest.fixture(autouse=True)로 정의된 고정 기기는 인수로 나열할 필요 없이 가용성 범위 내의 모든 테스트에서 자동으로 실행됩니다. 이 옵션은 테스트 종속성을 숨기므로 아쉽게 사용합니다.

임시 파일 설정 만들기

파일과 상호 작용하는 테스트를 작성할 때 다른 테스트와 충돌하지 않거나 하드 코딩된 위치에 의존하지 않는 격리된 경로가 필요한 것이 일반적입니다. Pytest는 고유한 디렉터리를 만들고 보존을 관리하기 때문에 일반적으로 새 테스트에 가장 적합한 기본 제공 임시 디렉터리 비품을 제공합니다. 그러나 작은 사용자 지정 설비를 만드는 것은 비품 요청을 이해하고 값을 반환하는 유용한 방법입니다.

다음 픽스처는 팩터리 함수를 반환합니다. 설치 중에는 고정 장치 자체가 파일을 생성하지 않습니다. 대신 중첩된 create() 함수는 tmp_file()를 테스트가 호출할 때 파일을 만듭니다.

from pathlib import Path
import tempfile

import pytest


@pytest.fixture
def tmp_file():
    def create(contents=""):
        temp = tempfile.NamedTemporaryFile(delete=False)
        try:
            path = Path(temp.name)
        finally:
            temp.close()

        path.write_text(contents, encoding="utf-8")
        return path

    return create

fixture는 Python tempfile.NamedTemporaryFile() 함수를 delete=False 사용하여 파일이 닫힌 후에도 계속 존재합니다. 테스트가 경로를 받기 전에 파일 핸들이 닫힙니다. 이는 특히 Windows 때 테스트가 파일을 다시 열거나 삭제해야 하는 경우에 중요합니다.

다음은 테스트가 설비를 사용하는 방법의 예입니다.

def test_file(tmp_file):
    path = tmp_file("ready")
    assert path.read_text(encoding="utf-8") == "ready"

이 설정 tmp_file 에서는 fixture 이름이고 테스트는 fixture가 반환하는 create() 함수를 받습니다. 테스트 내에서 호출 tmp_file() 하면 파일이 생성되고 요청된 내용이 작성되며 해당 파일에 대한 개체가 pathlib.Path 반환됩니다.

참고

픽스처를 컨텍스트 매니저와 함께 사용할 때는 주의해야 합니다. 테스트 픽스처가 with 블록 내부에서 경로 또는 핸들을 반환하는 경우, 콘텍스트 매니저는 테스트가 리소스를 사용하기 전에 이미 리소스를 닫거나 삭제할 수 있습니다. 테스트에서 관리되는 리소스가 필요하다면 컨텍스트 매니저를 yield 동안 계속 열어 두고, 반환하기 전에 의도적으로 리소스를 닫은 뒤 나중에 정리하세요.

범위 관리

기본적으로 pytest fixtures는 function 범위를 사용합니다. 함수 범위 픽스처는 테스트가 실행될 때마다 생성되었다가 해당 테스트가 완료되면 소멸합니다. 픽스처에서 teardown 코드를 사용하면, pytest는 해당 픽스처의 범위가 끝나는 시점에 그 코드를 실행합니다.

Pytest를 사용하면 더 광범위한 픽스처 범위를 통해 비용이 많이 드는 설정 작업을 최적화할 수 있습니다. 기본 제공 fixture 범위 이름은 다음과 같습니다.

  • function: 기본 범위입니다. 테스트 함수마다 한 번씩 픽스처가 생성됩니다.
  • class: 테스트 클래스마다 한 번씩 픽스처가 생성됩니다.
  • module: 고정물은 모듈에 대해 한 번 생성됩니다.
  • package: 해당 패키지와 모든 하위 디렉터리에 대한 테스트가 완료되면, 지정된 위치에서 픽스처의 생성과 해체가 단 한 번 수행됩니다.
  • session: 테스트 세션에 대해 픽스처는 한 번 생성됩니다.

고급 사례의 scope 경우 이러한 범위 이름 중 하나를 반환하는 호출 가능 항목일 수도 있습니다. Pytest는 fixture를 정의하고 키워드 인수 fixture_name 를 전달하는 경우 함수를 한 번 호출합니다 config. 자세한 내용은 동적 범위에 대한 pytest 문서를 참조하세요.

이 컨텍스트에서 한 번 생성 된다는 것은 pytest가 해당 범위에 대한 fixture 값을 캐시한다는 것을 의미합니다. 예를 들어, 모듈 범위의 픽스처는 한 모듈 내에서 여러 테스트에 의해 요청될 수 있지만, 이러한 테스트는 동일한 캐시된 픽스처 값을 받습니다. 매개 변수가 있는 비품은 예외입니다. pytest는 범위 내에서 설정된 각 매개 변수에 대해 새 값을 만들 수 있습니다.

설정 내용을 공유하는 것이 안전하고 유용하다고 판단되는 경우가 아니라면, 픽스처는 가급적 함수 범위 내로 제한하여 사용하는 것이 좋습니다. 변경 가능한 개체, 열린 연결, 환경 변경 및 파일은 더 넓은 범위에서 캐시될 때 테스트 간에 상태를 누설할 수 있습니다.

모듈 범위에서 tmp_file 픽스처의 모습은 다음과 같습니다.

from pathlib import Path
import tempfile

import pytest


@pytest.fixture(scope="module")
def tmp_file():
    def create(contents=""):
        temp = tempfile.NamedTemporaryFile(delete=False)
        try:
            path = Path(temp.name)
        finally:
            temp.close()

        path.write_text(contents, encoding="utf-8")
        return path

    return create

이 fixture는 팩터리 함수 scope="module" 를 반환하므로 모듈에 대한 팩터리 함수를 캐시합니다. 팩터리는 테스트가 호출 tmp_file()할 때마다 여전히 새 파일을 만듭니다. fixture가 파일 경로를 직접 만들고 반환하는 경우 모듈의 모든 테스트는 동일한 경로를 받게 됩니다.

정리 관리

이전 버전의 tmp_file fixture는 임시 파일을 만들지만 해당 파일은 제거하지 않습니다. Pytest는 간편한 정리 작업을 위해 yield fixture를 권장합니다. 설치를 수행하기 전 yield 의 코드이며 테스트에 값을 제공합니다. 설정이 완료된 후 테스트가 실패하더라도 yield 이후의 코드는 테어다운 과정에서 반드시 실행됩니다. 만약 yield 픽스처가 yield에 도달하기 전에 예외를 발생시키면, pytest는 그 yield 이후의 teardown 코드를 실행하지 않지만, 같은 테스트를 위해 성공적으로 완료된 이전 픽스처들은 여전히 정리합니다.

다음 버전은 팩터리에서 만든 모든 파일을 추적하고 테스트가 완료된 후 해당 파일을 삭제하려고 시도합니다.

from pathlib import Path
import tempfile

import pytest


@pytest.fixture
def tmp_file():
    paths = []

    def create(contents=""):
        temp = tempfile.NamedTemporaryFile(delete=False)
        try:
            path = Path(temp.name)
        finally:
            temp.close()

        paths.append(path)
        path.write_text(contents, encoding="utf-8")
        return path

    yield create

    for path in paths:
        try:
            path.unlink()
        except FileNotFoundError:
            pass

기본 function 범위를 사용하여 pytest는 요청하는 각 테스트 후에 정리 코드를 실행합니다 tmp_file. fixture를 scope="module"변경하면 pytest가 해당 모듈에서 테스트 실행을 완료한 후 모듈을 해체하는 동안 정리를 실행합니다. 여러 yield 비품이 활성화된 경우 pytest는 설정의 역순으로 해체 코드를 실행합니다.

테스트가 파일 자체를 삭제할 수 있기 때문에 정리 시 try/except FileNotFoundError가 사용됩니다. Windows에서는 테스트가 파일을 열어 둔 경우 PermissionError 과 함께 삭제가 실패할 수도 있으니, 테스트에서 생성한 핸들은 반드시 닫아 주세요. 더 많은 제어가 필요한 경우 정리에 사용할 request.addfinalizer() 수도 있습니다. 리소스가 생성된 이후에 파이널라이저를 등록해야 합니다. pytest는 픽스처에서 이후에 예외가 발생하더라도 이미 등록된 파이널라이저를 teardown 단계에서 실행하기 때문입니다. 간단한 정리를 할 때는 yield 픽스처가 보통 더 읽기 쉽습니다.

conftest.py 사용

각 테스트 파일에서 비품을 정의하는 대신 공유 비품을 이름이 지정된 conftest.py파일에 저장할 수 있습니다. conftest.py의 픽스처는 명시적인 임포트 없이도 같은 디렉토리와 그 하위 디렉토리의 테스트에서 자동으로 사용 가능합니다.

테스트 도구 모음에 여러 conftest.py 파일을 가질 수 있습니다. Pytest는 요청한 테스트의 관점에서 픽스처 가용성을 확인합니다. 테스트 클래스 또는 모듈의 픽스처는 해당 로컬 범위에서 고려된 다음, 동일한 디렉터리 및 상위 디렉터리의 conftest.py 파일에 있는 픽스처가 순차적으로 고려됩니다. 테스트는 상위 디렉토리에서 픽스처를 검색할 수 있지만, 동일한 수준이나 하위 디렉토리로 내려가며 검색하지는 않습니다. 플러그인 픽스처는 로컬 픽스처 이후에 검색됩니다. 테스트 파일에서 conftest.py 가져오지 말고 pytest에서 검색하도록 합니다.

기본 제공 구성 요소 탐색

Pytest에는 테스트를 간소화하도록 설계된 많은 기본 제공 설비가 있습니다. 이러한 비품은 일반적인 설정 및 정리 작업을 처리하므로 테스트 어설션 작성에 집중할 수 있습니다. 전체 목록을 검토하려면 기본 제공 비품에 대한 pytest 참조를 참조하세요.

주요 기본 제공 비품은 다음과 같습니다.

  • cache: pytest 실행에서 값을 저장하고 검색합니다.
  • capsys: stdout, stderr에 기록된 텍스트를 캡처합니다.
  • tmp_path: 각 매개 변수화된 사례를 포함하여 각 테스트 함수 호출에 고유한 임시 디렉터리에 대한 개체를 제공합니다 pathlib.Path .
  • tmpdir: 임시 디렉터리를 레거시 py.path.local 개체로 제공합니다. 새 테스트의 경우 pytest에서 권장합니다 tmp_path.
  • monkeypatch: 클래스, 함수, 사전, 환경 변수 및 기타 개체를 일시적으로 수정합니다.
  • request: 현재 진행 중인 테스트 또는 설비에 관한 정보를 제공하며 종료 작업을 지원합니다.

일반적으로 사용되는 다른 기본 제공 픽스처에는 caplog (로그 레코드 캡처), capfd (파일 설명자 수준 출력 캡처), tmp_path_factory (세션 범위 임시 디렉터리 만들기), 및 pytestconfig (활성 pytest 구성 노출) 등이 있습니다. 전체 목록은 기본 제공 비품에 대한 pytest 참조를 참조하세요.

다음 연습에서는 tmp_pathpathlib.Path를 사용하여 테스트별 디렉터리에 임시 파일을 생성합니다. 기존 pytest 프로젝트에서 tmpdir를 볼 수도 있지만, 새 코드에서는 tmp_path를 사용하는 것이 좋습니다. 기본적으로 pytest는 마지막 몇 번의 테스트 실행에서 임시 디렉터리를 유지하므로 실행 후 최근 pytest 관리 임시 폴더를 볼 수 있습니다. 자세한 내용은 임시 디렉터리 및 파일에 대한 pytest 설명서를 참조하세요.

테스트에서 몽키패칭의 역할

이전 테스트 픽스처는 테스트 리소스의 생성과 정리를 담당했습니다. 비품의 또 다른 일반적인 사용은 외부 상태에서 코드를 격리해야 하는 테스트의 임시 상태 수정 입니다. 이러한 경우를 위해 pytest는 내장 monkeypatch 픽스처를 제공하는데, 이는 pytest 픽스처인 동시에 자동 정리의 좋은 예시이기도 합니다.

외부 리소스, 전역 구성 또는 운영 체제 상태에 종속된 코드를 테스트하는 것은 어려울 수 있습니다. monkeypatch 픽스처를 사용하면 테스트를 수행하는 동안 속성, 딕셔너리 값, 환경 변수, 현재 작업 디렉터리 또는 sys.path 등을 일시적으로 변경할 수 있습니다. Pytest는 요청 테스트 함수 또는 설비가 완료된 후 해당 변경 내용을 자동으로 실행 취소합니다.

다음은 한 테스트에 대한 작은 프로젝트 모듈의 함수 특성을 대체하는 예제입니다. 범위가 좁은 애플리케이션 코드(pytest 자체에서 사용하는 표준 라이브러리 함수 대신)를 패치하면 테스트가 집중되고 실수로 pytest의 동작이 손상되지 않도록 방지합니다.

# project_module.py
def get_user_id():
    # Imagine this calls a remote service.
    return "real-user"
# test_project.py
import project_module


def test_user_id(monkeypatch):
    monkeypatch.setattr(project_module, "get_user_id", lambda: "test-user")
    assert project_module.get_user_id() == "test-user"

대상을 점선 가져오기 경로 문자열로 지정할 수도 있습니다. Pytest는 패치를 적용할 때 명명된 모듈을 확인하고 가져옵니다. 이 모듈은 테스트 파일에 모듈이 아직 필요하지 않은 경우에 편리합니다. 이 예제에서는 테스트가 어설션을 위해 패치된 함수를 참조할 수 있도록 나중의 import project_module 부분이 포함됩니다.

# test_project.py
def test_user_id_by_path(monkeypatch):
    monkeypatch.setattr("project_module.get_user_id", lambda: "test-user")

    import project_module
    assert project_module.get_user_id() == "test-user"

setattr()을 통한 속성 설정 및 메서드 재정의 외에도, monkeypatch 픽스처는 delattr()을 통한 속성 삭제, setenvdelenv를 이용한 환경 변수 설정 및 삭제, setitemdelitem을 통한 딕셔너리 항목 설정 및 삭제, syspath_prepend를 통한 sys.path 앞에 값 추가, chdir을 이용한 현재 작업 디렉터리 변경, 그리고 context()을 활용한 중첩 패칭 스코프 생성 등 다양한 기능을 제공합니다. raising=Truesetattr()의 기본 delattr() 동작은 맞춤법이 틀린 대상을 잡아내는 데 도움이 됩니다. 테스트나 픽스처 정리 작업의 나머지 부분이 계속되기 전에 패치를 취소해야 할 때는 monkeypatch.context() 을 사용하세요. 자세한 내용은 원숭이 패치에 대한 pytest 설명서를 참조하세요.

강력하지만 monkeypatch 신중하게 사용합니다.

  • 코드 명확성 및 유지 관리: 복잡한 방법으로 과도하게 사용 monkeypatch 하거나 사용하면 테스트를 이해하고 유지 관리하기가 더 어려워질 수 있습니다.
  • 테스트 유효성: Monkeypatching은 프로덕션 동작과 매우 다른 인공 조건에서 통과하는 테스트를 만들 수 있습니다.
  • 구현 세부 정보에 대한 과잉 의존: 내부 구현 세부 정보를 패치하는 테스트는 기본 코드가 변경될 때 취약해질 수 있습니다.
  • 디버깅 복잡성: 패치가 기본 애플리케이션 동작을 변경하는 경우 사용하는 monkeypatch 테스트 디버깅이 더 어려울 수 있습니다.

포커스가 있는 동작을 격리하고 각 패치를 가능한 한 작고 명시적인 상태로 유지하는 데 도움이 되는 경우에 사용합니다 monkeypatch .