# pytest　一時ディレクトリを使いダミーファイルでテスト


キーワード： `tmp_path`, `monkeypatch.chdir`

## できること

テスト時は実際にファイルを参照せずに、ダミーファイルから参照させるようにする。

下記ディレクトリ構成で、 `input/sample.csv` が通常実行時に参照されるファイル。テスト時には、参照しないようにする。

```
❯ tree -L 2 .
.
├── input
│   └── sample.csv
├── requirements.txt
├── src
│   └── app.py
└── tests
    ├── __init__.py
    └── test_app.py
```

## コード

```python
import csv
import os

def main():
    print(os.getcwd())
    with open('input/sample.csv', 'r') as file:
        reader = csv.reader(file)
        for row in reader:
            print(row)

if __name__ == '__main__':
    main()
```

```python
import pytest
import csv
import os

@pytest.fixture
def set_test_csv(tmp_path):
    rows = [['Name', 'Age', 'City'],
            ['John', '30', 'New York'],
            ['Jane', '25', 'London']]
    os.mkdir(tmp_path / 'input')
    csv_file_name = tmp_path / 'input/sample.csv'
    with open(csv_file_name, 'w', newline='') as file:
        writer = csv.writer(file)
        writer.writerows(rows)

@pytest.fixture
def main(tmp_path, monkeypatch):
    monkeypatch.chdir(tmp_path)
    from src.app import main
    return main

def test_app(tmp_path, set_test_csv, main):
    main()
    assert True
```

### 解説

app.pyの `main` では、ハードコーディングで `input/sample.csv` が指定されている。（こんなクソコードがないという話は置いておく）

test_app.pyの `set_test_csv` でダミーのテストファイルを作成している。 `tmp_path` (pytestの組込みfixture)という一時ディレクトリ配下に同じディレクトリを掘りファイル作成。

★ `def main` の実装がポイント。

- fixtureとして定義
- `tmp_path` , `monkeypath.chdir` でカレントディレクトリを `tmp_path` に変更
- カレントディレクトリ変更後に、テスト対象のモジュールから関数をimport

main自体をfixtureにしているのは、 `monkeypatch.chdir(tmp_path)` のロジックをimport前に差し込みたいため。
これより前にimport すると実際のファイルを参照してしまう。

`test_app` のフローは、下記のような感じ。

1. `set_test_csv`  でダミーファイルを作成（fixtureの引数）
2. `main` でカレントディレクトリを一時ディレクトリに置き換えて、テスト対象関数のimport
3. `main()` で関数を実行。→このとき、ダミーファイルが参照される
4. 要件に沿って `assert` 

## 最後に

[tmp_path_factory](https://docs.pytest.org/en/7.1.x/how-to/tmp_path.html#the-tmp-path-factory-fixture) がよくわかってないので、場合によっては使うべきかも。

> The tmp_path_factory is a session-scoped fixture which can be used to create arbitrary temporary directories from any other fixture or test.
> 

## notion AI改善バージョン

以下、notion AIに改善してもらったバージョン。

です・ます調になっただけかな？？

### できること

テスト時には、実際のファイルを参照せずに、ダミーファイルから参照させることができます。

下記ディレクトリ構成で、`input/sample.csv` は通常実行時に参照されるファイルです。テスト時には、参照しないようにする必要があります。

```
❯ tree -L 2 .
.
├── input
│   └── sample.csv
├── requirements.txt
├── src
│   └── app.py
└── tests
    ├── __init__.py
    └── test_app.py

```

### コード

```
import csv
import os

def main():
    print(os.getcwd())
    with open('input/sample.csv', 'r') as file:
        reader = csv.reader(file)
        for row in reader:
            print(row)

if __name__ == '__main__':
    main()

```

```
import pytest
import csv
import os

@pytest.fixture
def set_test_csv(tmp_path):
    rows = [['Name', 'Age', 'City'],
            ['John', '30', 'New York'],
            ['Jane', '25', 'London']]
    os.mkdir(tmp_path / 'input')
    csv_file_name = tmp_path / 'input/sample.csv'
    with open(csv_file_name, 'w', newline='') as file:
        writer = csv.writer(file)
        writer.writerows(rows)

@pytest.fixture
def main(tmp_path, monkeypatch):
    monkeypatch.chdir(tmp_path)
    from src.app import main
    return main

def test_app(tmp_path, set_test_csv, main):
    main()
    assert True

```

### 解説

`app.py` の `main` では、ハードコーディングで `input/sample.csv` が指定されています。（こんなクソコードがないという話は置いておきます）

`test_app.py` の `set_test_csv` でダミーのテストファイルを作成しています。 `tmp_path` （pytestの組込みfixture）という一時ディレクトリ配下に同じディレクトリを掘り、ファイルを作成します。

★ `def main` の実装がポイントです。

- fixtureとして定義
- `tmp_path` と `monkeypatch.chdir` でカレントディレクトリを `tmp_path` に変更
- カレントディレクトリ変更後に、テスト対象のモジュールから関数をimport

`main` 自体をfixtureにしているのは、 `monkeypatch.chdir(tmp_path)` のロジックをimport前に差し込みたいためです。

これより前にimport すると実際のファイルを参照してしまいます。

`test_app` のフローは、下記のような感じです。

1. `set_test_csv` でダミーファイルを作成（fixtureの引数）
2. `main` でカレントディレクトリを一時ディレクトリに置き換えて、テスト対象関数のimport
3. `main()` で関数を実行。→このとき、ダミーファイルが参照されます
4. 要件に沿って `assert`

### 最後に

[tmp_path_factory](https://docs.pytest.org/en/7.1.x/how-to/tmp_path.html#the-tmp-path-factory-fixture) がよくわかっていないので、場合によっては使うべきかもしれません。

> The tmp_path_factory is a session-scoped fixture which can be used to create arbitrary temporary directories from any other fixture or test.
>