0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

tempfileモジュールによる生成先をpytestの一時ディレクトリの下にする方法

Posted at

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"の下に生成される
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?