연습
이 연습에서는 예외가 예상되는 경우를 포함하여 함수를 테스트하는 데 사용합니다 pytest@pytest.mark.parametrize . 그런 다음, xUnit 스타일 setup_method() 및 teardown_method() 접근 방식의 클래스 기반 테스트를 fixture로 리팩터링합니다. 매개 변수화 및 설비를 사용하면 반복을 줄이고 보다 명확한 설정으로 더 많은 사례를 다루는 테스트를 작성할 수 있습니다.
설치 도움말은 가상 환경에 대한 Python 설명서, Pip 설명서를, 그리고 pytest 설치에 대한 pytest 설명서를 참조하세요. 이 연습의 pytest 기능에 대한 자세한 내용은 테스트 함수 매개 변수화에 대한 pytest 설명서, 예상된 예외, 비품, xUnit 스타일 설정 및 임시 디렉터리 및 파일에대한 어설션을 참조하세요.
시작하기 전에
모듈 필수 구성 요소에 설명된 대로 Python 3.10 이상을 사용합니다. pytest가 설치되고 활성화된 가상 환경이 이미 있는 경우 1단계로 계속 진행합니다. 그렇지 않으면 test_advanced.py 만들 폴더에서 가상 환경을 만들고 활성화합니다.
Windows PowerShell에서:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install pytest
python -m pytest --version
macOS, Linux 또는 Linux용 Windows 하위 시스템:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install pytest
python -m pytest --version
버전 명령은 다음 출력과 유사한 pytest 버전을 인쇄해야 합니다.
pytest x.y.z
설치 명령은 pytest를 특정 버전에 고정하지 않습니다. Pip는 패키지 메타데이터를 사용하여 가상 환경의 Python 버전과 호환되는 안정적인 pytest 릴리스를 선택합니다.
이 연습 전체에서 python -m pytest을(를) 사용하여 pytest가 활성 환경에서 실행되도록 하십시오. Windows PowerShell이 Activate.ps1 차단하는 경우 PowerShell 실행 정책 옵션에 대한 Python venv 설명서를 참조하거나 pip 및 pytest 명령에 대해 가상 환경의 Python 직접 실행합니다. 다음은 그 예입니다.
.\.venv\Scripts\python -m pip install --upgrade pip
.\.venv\Scripts\python -m pip install pytest
.\.venv\Scripts\python -m pytest -v test_advanced.py
1단계 - 이 연습을 위해 테스트 파일 추가
pytest를 실행할 동일한 폴더에서 test_advanced.py이라는 새 테스트 파일을 만듭니다. .venv 폴더 내에 파일을 배치하지 마세요. 다음 코드를 추가합니다.
import pytest def str_to_bool(string): normalized = string.lower() if normalized in ["yes", "y", "1"]: return True if normalized in ["no", "n", "0"]: return False raise ValueError(f"Cannot convert {string!r} to a boolean")함수
str_to_bool()는 문자열을 입력으로 허용합니다. 인식된 true 값에 대해True을 반환하고, 인식된 false 값에 대해False을 반환하며, 다른 값에는ValueError를 발생시킵니다.동일한 파일에서
str_to_bool()함수에 대한 테스트를 추가합니다. 먼저 실제 값을 테스트하는 데 사용합니다@pytest.mark.parametrize.@pytest.mark.parametrize("string", ["Y", "y", "1", "YES"]) def test_str_to_bool_true(string): assert str_to_bool(string) is True다음으로, 거짓 값으로 다른 테스트를 추가합니다.
@pytest.mark.parametrize("string", ["N", "n", "0", "NO"]) def test_str_to_bool_false(string): assert str_to_bool(string) is False이제
True및False반환 값을 위한 대표 입력을 다루는 두 개의 테스트 함수가 있습니다.마지막으로 함수가 수락해서는 안 되는 값에 대해 매개 변수가 있는 테스트를 추가합니다.
pytest.raises()를 컨텍스트 관리자로 사용하여str_to_bool()가 예상된 예외를 발생시킬 때만 테스트가 통과되도록 하십시오.@pytest.mark.parametrize("string", ["maybe", "2"]) def test_str_to_bool_invalid(string): with pytest.raises(ValueError, match="Cannot convert"): str_to_bool(string)인수는
match정규식을 사용하여 예외 메시지를 확인합니다. 여기서 테스트는 메시지의 안정적인 부분만 확인하므로 정확한 잘못된 값에 의존하지 않습니다.
참고
간단히 하기 위해 이 연습에서는 코드를 테스트 중인 상태로 유지하고 테스트를 동일한 파일로 유지합니다. 실제 Python 프로젝트에서 애플리케이션 코드와 테스트는 일반적으로 src/ 패키지 및 tests/ 디렉터리와 같은 다른 파일 및 디렉터리로 구분됩니다.
2단계 - 테스트 실행 및 보고서 살펴보기
테스트를 추가한 후 pytest를 실행하고 출력을 검사합니다. 각 입력 값을 별도의 테스트로 처리할 수 있도록 증가된 세부 정보 표시 플래그(-v)를 사용합니다.
Windows PowerShell에서:
python -m pytest -v test_advanced.py
macOS, Linux 또는 Linux용 Windows 하위 시스템에서:
python -m pytest -v test_advanced.py
출력은 다음 보고서와 유사해야 합니다.
============================= test session starts ==============================
platform ... -- Python 3.x.y, pytest-x.y.z, pluggy-x.y.z -- ...
rootdir: ...
...
collected 10 items
test_advanced.py::test_str_to_bool_true[Y] PASSED [ 10%]
test_advanced.py::test_str_to_bool_true[y] PASSED [ 20%]
test_advanced.py::test_str_to_bool_true[1] PASSED [ 30%]
test_advanced.py::test_str_to_bool_true[YES] PASSED [ 40%]
test_advanced.py::test_str_to_bool_false[N] PASSED [ 50%]
test_advanced.py::test_str_to_bool_false[n] PASSED [ 60%]
test_advanced.py::test_str_to_bool_false[0] PASSED [ 70%]
test_advanced.py::test_str_to_bool_false[NO] PASSED [ 80%]
test_advanced.py::test_str_to_bool_invalid[maybe] PASSED [ 90%]
test_advanced.py::test_str_to_bool_invalid[2] PASSED [100%]
============================== 10 passed in 0.01s ==============================
운영 체제, Python 버전, pytest 버전, 실행 경로, 루트 디렉터리, 플러그 인 및 캐시 줄, 컬렉션 진행률 서식, 줄 바꿈 및 런타임이 다를 수 있습니다. 테스트 함수는 세 개만 작성했지만 매개 변수가 있는 각 입력 값이 자체 테스트 사례로 실행되기 때문에 pytest는 10개의 테스트를 수집했습니다.
3단계 - 고정 설정으로 설정 및 해체 작업 리팩터링
클래스 기반 테스트를 test_advanced.py 파일에 추가합니다. 이 테스트는 xUnit 스타일
setup_method()및teardown_method()메서드를 사용하여 각 테스트 메서드 주위에 파일을 만들고 제거합니다.from pathlib import Path class TestFile: def setup_method(self): self.path = Path("done") self.path.write_text("1", encoding="utf-8") def teardown_method(self): self.path.unlink(missing_ok=True) def test_done_file(self): contents = self.path.read_text(encoding="utf-8") assert contents == "1"이 테스트는 통과할 수 있지만 현재 작업 디렉터리에 고정 파일 이름을 쓰고 별도의 정리 코드가 필요합니다. 로컬 실행과 같이
pytest-xdist테스트 실행이 중단되거나 테스트가 동시에 실행되는 경우 이 방법은 부실 파일을 그대로 두거나, 완료된 기존 파일을 덮어쓰거나, 테스트가 서로 영향을 줄 수 있습니다.기본 제공
tmp_path설비를 사용하여 임시 파일을 만드는 모듈 수준 설비를 추가합니다. 다음 단계에서는 설정 및 해체 메서드 대신 이 설비를 사용하도록 클래스를 업데이트합니다. 가독성을 위해 매개변수가str_to_bool있는 테스트 뒤에, 그리고 클래스TestFile앞에 이 모듈 수준의 고정물을 배치합니다. Pytest는 모듈에 표시되는 위치에 관계없이 모듈 수준 비품을 검색하므로 위치는 pytest가 아닌 인간 판독기를 위한 것입니다.@pytest.fixture def tmp_file(tmp_path): def write(): path = tmp_path / "done" path.write_text("1", encoding="utf-8") return path return writetmp_file()고정 장치는 pytest의tmp_pathfixture를 사용하여 각 매개변수화된 사례를 포함한 각 테스트 함수 호출마다 고유한pathlib.Path임시 디렉터리를 제공합니다. Pytest는 임시 디렉터리를 관리하므로 테스트는 하드 코딩된 파일 경로에 의존하지 않습니다.기존의
TestFile클래스와 그 안의setup_method(),teardown_method()메서드를 삭제하고, 설정 및 해체 메서드 대신 픽스처를 사용하는 최종 버전으로 대체하세요.class TestFile: def test_done_file(self, tmp_file): path = tmp_file() contents = path.read_text(encoding="utf-8") assert contents == "1"테스트가 더 이상 개체를
from pathlib import Path직접 생성하지 않으므로 이전 단계에서 추가한 가져오기를Path제거할 수도 있습니다.
작업 확인
이제 다음을 포함하는 test_advanced.py라는 Python 파일이 있어야 합니다.
-
str_to_bool()문자열을 수락하고 인식된 true 및 false 값에 대한 부울 값을 반환하는 함수입니다. -
str_to_bool()함수에 대한 세 가지 매개변수화된 테스트:True값을 테스트하는 테스트,False값을 테스트하는 테스트, 잘못된 값을 확인하여ValueError를 발생시키는 테스트. - 내장된
tmp_path픽스처를 활용하여 일부 내용이 담긴 임시 작업 완료 파일을 생성하는 사용자 지정 pytest 설정입니다. - 사용자 지정
TestFile설비를 사용하여 파일을 만들고 읽는 하나의 메서드가 있는 단일tmp_file클래스입니다. -
from pathlib import Path최종 버전에서는 가져오기 및 메서드가 없습니다setup_method()/teardown_method().
최종 파일은 다음과 유사하게 표시됩니다.
import pytest
def str_to_bool(string):
normalized = string.lower()
if normalized in ["yes", "y", "1"]:
return True
if normalized in ["no", "n", "0"]:
return False
raise ValueError(f"Cannot convert {string!r} to a boolean")
@pytest.mark.parametrize("string", ["Y", "y", "1", "YES"])
def test_str_to_bool_true(string):
assert str_to_bool(string) is True
@pytest.mark.parametrize("string", ["N", "n", "0", "NO"])
def test_str_to_bool_false(string):
assert str_to_bool(string) is False
@pytest.mark.parametrize("string", ["maybe", "2"])
def test_str_to_bool_invalid(string):
with pytest.raises(ValueError, match="Cannot convert"):
str_to_bool(string)
@pytest.fixture
def tmp_file(tmp_path):
def write():
path = tmp_path / "done"
path.write_text("1", encoding="utf-8")
return path
return write
class TestFile:
def test_done_file(self, tmp_file):
path = tmp_file()
contents = path.read_text(encoding="utf-8")
assert contents == "1"
테스트 파일을 다시 실행합니다.
Windows PowerShell에서:
python -m pytest -v test_advanced.py
macOS, Linux 또는 Linux용 Windows 하위 시스템:
python -m pytest -v test_advanced.py
최종 보고서에는 11개의 수집된 테스트( 매개 변수가 있는 테스트의 10건 및 클래스 기반 테스트 1건)가 표시됩니다.
============================= test session starts ==============================
platform ... -- Python 3.x.y, pytest-x.y.z, pluggy-x.y.z -- ...
rootdir: ...
...
collected 11 items
test_advanced.py::test_str_to_bool_true[Y] PASSED [ 9%]
test_advanced.py::test_str_to_bool_true[y] PASSED [ 18%]
test_advanced.py::test_str_to_bool_true[1] PASSED [ 27%]
test_advanced.py::test_str_to_bool_true[YES] PASSED [ 36%]
test_advanced.py::test_str_to_bool_false[N] PASSED [ 45%]
test_advanced.py::test_str_to_bool_false[n] PASSED [ 54%]
test_advanced.py::test_str_to_bool_false[0] PASSED [ 63%]
test_advanced.py::test_str_to_bool_false[NO] PASSED [ 72%]
test_advanced.py::test_str_to_bool_invalid[maybe] PASSED [ 81%]
test_advanced.py::test_str_to_bool_invalid[2] PASSED [ 90%]
test_advanced.py::TestFile::test_done_file PASSED [100%]
============================== 11 passed in 0.01s ==============================
모든 테스트는 오류 없이 통과해야 합니다.