tempfile
モジュールで生成される一時ファイル・ディレクトリを、pytestのtmp_path
の下に作る方法です。
tempfile
モジュールを使っているプログラム(=テスト対象)の中で生成される一時ファイル・ディレクトリの生成場所を切り替えたいというケースに対するanswerです。
単にユニットテストで使う一時ファイル・ディレクトリを準備するだけならpytestのtmp_path
をそのまま使ってください。
間違った方法(予期しない副作用がある)
どこに一時ファイル・ディレクトリが生成されるかは環境変数TEMPDIRで決まりますが、その内容はtempfile.gettempdir()
にキャッシュされているのでtempfile.tempdir = None
でキャッシュをクリアしないと(環境変数TMPDIRを書き換えても)切り替えられません。
しかし、キャッシュされているので一度切り替えてしまうと、別のテスト関数でも切り替わった状態が反映されてしまいます。
BAD CASE
import tempfile
from pathlib import Path
from _pytest.monkeypatch import MonkeyPatch
class TestBadCase:
def test_foo(self, tmp_path: Path, monkeypatch: MonkeyPatch):
# tempfileモジュールが一時ファイル・ディレクトリを生成するディレクトリを
# pytestが生成する一時ディレクトリの下に切り替える
monkeypatch.setenv("TMPDIR", str(tmp_path))
tempfile.tempdir = None
with tempfile.NamedTemporaryFile() as f:
print(f.name) # GOOD: tmp_pathの下に生成される
def test_bar(self):
with tempfile.NamedTemporaryFile() as f:
print(f.name) # NOT GOOD: "/tmp"では無く、test_foo()で設定されたディレクトリの下に生成される
解決方法
- テスト関数の入口で
tempfile.tempdir = None
して環境変数TMPDIRの設定が有効になるようにする。 - テスト関数の出口で
tempfile.tempdir = None
して、次に実行されるテスト関数で環境変数TMPDIRが再評価されるようにする。 - テスト関数の先頭と末尾でそれ(↑)をやると、テストが失敗した時に出口処理が実行されないのでtry-finallyで囲む、、、ぐらいならcontextmanagerを使おう。
-
pytest.fixture
はcontextmanagerなのでteardown側に出口処理を書こう。
-
GOOD CASE
import tempfile
from pathlib import Path
from _pytest.monkeypatch import MonkeyPatch
class TestGoodCase:
@pytest.fixture(scope="function")
def switch_tempdir(self, tmp_path: Path, monkeypatch: MonkeyPatch):
# tempfileモジュールが一時ファイル・ディレクトリを生成するディレクトリを
# pytestが生成する一時ディレクトリの下に切り替える
monkeypatch.setenv("TMPDIR", str(tmp_path))
tempfile.tempdir = None
yield tmp_path
tempfile.tempdir = None
def test_foo(self, switch_tempdir: Path):
with tempfile.NamedTemporaryFile() as f:
assert Path(f.name).parent == switch_tempdir
print(f.name)
def test_bar(self, switch_tempdir: Path):
with tempfile.NamedTemporaryFile() as f:
assert Path(f.name).parent == switch_tempdir
print(f.name) # GOOD: test_foo()とは異なるディレクトリの下に生成される
def test_baz(self):
with tempfile.NamedTemporaryFile() as f:
print(f.name) # GOOD: "/tmp"の下に生成される