pytest 임시 디렉터리·파일(tmp_path) 활용하기
pytest 임시 디렉터리·파일(tmp_path) 활용하기
테스트가 파일이나 디렉터리를 만들고 지우는 작업을 해야 한다면, 직접 임시 경로를 관리하느라 고생할 필요 없어요. pytest가 제공하는 tmp_path 픽스처가 테스트 함수마다 고유한 임시 디렉터리를 만들어주고, 테스트가 끝나면 알아서 정리해줍니다. 여기서는 tmp_path부터 세션 범위의 tmp_path_factory, 그리고 임시 디렉터리의 위치·보존 규칙까지 살펴볼게요.
출처: How to use temporary directories and files in tests — pytest 공식 문서
본문
tmp_path 픽스처
tmp_path 픽스처는 테스트 함수마다 고유한 임시 디렉터리를 제공해줘요. tmp_path는 pathlib.Path 객체입니다. 사용 예시:
# content of test_tmp_path.py
CONTENT = "content"
def test_create_file(tmp_path):
d = tmp_path / "sub"
d.mkdir()
p = d / "hello.txt"
p.write_text(CONTENT, encoding="utf-8")
assert p.read_text(encoding="utf-8") == CONTENT
assert len(list(tmp_path.iterdir())) == 1
assert 0
실행하면 마지막 assert 0 줄 덕분에 값들을 확인하기 위해 실패한 테스트를 볼 수 있어요.
$ pytest test_tmp_path.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_tmp_path.py F [100%]
================================= FAILURES =================================
_____________________________ test_create_file _____________________________
tmp_path = PosixPath('PYTEST_TMPDIR/test_create_file0')
def test_create_file(tmp_path):
d = tmp_path / "sub"
d.mkdir()
p = d / "hello.txt"
p.write_text(CONTENT, encoding="utf-8")
assert p.read_text(encoding="utf-8") == CONTENT
assert len(list(tmp_path.iterdir())) == 1
> assert 0
E assert 0
test_tmp_path.py:11: AssertionError
========================= short test summary info ==========================
FAILED test_tmp_path.py::test_create_file - assert 0
============================ 1 failed in 0.12s =============================
기본적으로 pytest는 마지막 3번의 pytest 호출에 대한 임시 디렉터리를 보존해요. 같은 테스트 함수의 동시 실행은 각 동시 실행마다 고유한 기본 임시 디렉터리를 구성함으로써 지원됩니다. 상세는 temporary directory location and retention을 참고하세요.
tmp_path_factory 픽스처
tmp_path_factory는 세션 범위 픽스처로, 다른 어떤 픽스처나 테스트에서도 임의의 임시 디렉터리를 만들 때 쓸 수 있어요. 예를 들어 테스트 스위트가 절차적으로 생성되는 큰 이미지 파일을 필요로 한다고 해볼게요. 각 테스트가 자기 tmp_path에 같은 이미지를 계산하는 대신 세션당 한 번 생성해 시간을 아낄 수 있습니다.
# contents of conftest.py
import pytest
@pytest.fixture(scope="session")
def image_file(tmp_path_factory):
img = compute_expensive_image()
fn = tmp_path_factory.mktemp("data") / "img.png"
img.save(fn)
return fn
# contents of test_image.py
def test_histogram(image_file):
img = load_image(image_file)
# compute and test histogram
tmpdir·tmpdir_factory 픽스처
tmpdir과 tmpdir_factory 픽스처는 tmp_path·tmp_path_factory와 비슷하지만, 표준 pathlib.Path 객체 대신 레거시 py.path.local 객체를 쓰고 반환해요.
요즘은 tmp_path와 tmp_path_factory를 쓰는 게 권장됩니다. 옛 코드 베이스를 현대화하는 데 도움을 주고 싶다면 legacypath 플러그인을 끈 상태로 pytest를 실행할 수 있어요.
pytest -p no:legacypath
이러면 레거시 경로를 쓰는 테스트에서 오류가 발생합니다. config 파일의 addopts 인자로 영구히 설정할 수도 있어요.
임시 디렉터리 위치와 보존
tmp_path와 (이제 deprecated된) tmpdir 픽스처가 반환하는 임시 디렉터리는 --basetemp 옵션에 따라 달라지는 구조로, 기본 임시 디렉터리 아래에 자동 생성돼요.
-
기본값(
--basetemp옵션을 설정하지 않은 경우)의 임시 디렉터리는 다음 템플릿을 따릅니다.{temproot}/pytest-of-{user}/pytest-{num}/{testname}/여기서 각 항목은:
{temproot}:tempfile.gettempdir로 결정되는 시스템 임시 디렉터리.PYTEST_DEBUG_TEMPROOT환경변수로 재정의할 수 있어요.{user}: 테스트를 실행하는 사용자 이름{num}: 테스트 스위트 실행 때마다 증가하는 숫자{testname}: 현재 테스트 이름을 살균(sanitize)한 것
자동 증가하는
{num}자리 표시자는 기본적인 보존 기능을 제공해, 이전 테스트 실행 결과를 무작정 제거하지 않게 해줘요. 기본적으로 마지막 3개의 임시 디렉터리가 유지되는데,tmp_path_retention_count와tmp_path_retention_policy로 설정할 수 있습니다. -
--basetemp옵션을 쓰면(예:pytest --basetemp=mydir) 그 값이 기본 임시 디렉터리로 직접 사용돼요.{basetemp}/{testname}/이 경우 보존 기능이 없다는 점에 주의하세요. 가장 최근 실행 결과만 유지됩니다.
--basetemp에 준 디렉터리는 매 테스트 실행 전에 무조건 비워지므로, 그 목적으로만 쓸 디렉터리를 사용하세요.
pytest-xdist로 로컬 머신에서 테스트를 분산 실행할 때는 모든 임시 데이터가 테스트 실행 하나의 임시 디렉터리 아래에 모이도록 서브 프로세스용 basetemp 디렉터리를 자동으로 구성해줍니다.