ざっくり分かるpytestのfixture

## pytestにおけるfixtureとは

- テストの事前処理、事後処理を記載できるpytestの機能です。
- SetUp(事前処理), TearDown(事後処理)を1つの関数で書けます。
- `setup_class`, `setup_function`のように、スコープごとに違う関数を呼ぶ必要がありません。
- fixtureからfixtureを呼び出すことができ、処理を階層的に記述できます。

## SetUp, TearDownじゃダメなの？

- ダメかと言われるとダメじゃありません。チーム内でスタイルが統一されている方が大事です。
- 機能面だけで言うと、pytestにおいてはfixtureを利用する方がリッチではあります。
- ブラウザ操作自動化ツールである[Playwright for Python](https://playwright.dev/python/)はfixtureを利用しています。
    - 上記を採用する場合、可読性と保守性の観点からfixtureに統一することを勧めます。

## 基本的な構文

```python
import pytest


@pytest.fixture(scope="function")  # fixtureの宣言
def hoge():
    # setup
    message = "ほげ"

    # test
    yield message

    # teardown
    print("done")

def test_print(hoge):
    print(hoge)
```

>実行結果:
ほげ
done

上記のように、
- `@pytest.fixture(scope="~~~")` とデコレート文を書いた次の関数がfixtureになります。
- `scope="~~~"`は実行の粒度を指します。[（参考)](https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#scope-sharing-fixtures-across-classes-modules-packages-or-session)
    - "function" : テストメソッドごと
    - "class" : テストクラスごと
    - "module" : スクリプト(ファイル)ごと
    - "session" : pytestコマンド実行ごと
- 呼ぶ側は引数にfixtureを指定します。
- fixture側で`yield` (続く処理がない場合は`return`)すると呼び出し元に値を引き渡せます。

とりあえずこれだけ知っておけば最低限使えると思います。

## 実例: fixtureを使ったディレクトリ作成①　autouse=True

ではもう少し深堀りして、fixtureを使ったディレクトリ作成を試します。
これはテストのスクリーンショットを実行ごとに保存する際などに使えます。

ここでは下記のディレクトリ構成を想定し、テスト実行時にその実行日時のディレクトリを`outputs/`以下に生成します。

```shell
.
    tests/
        outputs/
            {%y%m%d-%H%M%S}/  # 今回の作成対象ディレクトリ
        conftest.py
        test_makedir.py
````

fixtureは設定ファイル`conftest.py`中に記載すると全てのテストスクリプトから呼び出せます。

```conftest.py
import os
from datetime import datetime
 
import pytest
 
PATH_OUTPUTS = "./tests/outputs"
 
 @pytest.fixture(scope="session", autouse=True)
 def parentdir():
     name_parentdir = datetime.now().strftime("%y%m%d-%H%M%S")
     path_parentdir = os.path.join(PATH_OUTPUTS, name_parentdir)
     os.makedirs(path_parentdir)
     return path_parentdir
```

```test_makedir.py
class TestMakeDir:
    def test_makedir(self): # 以下省略
```

テストメソッドの引数に`parentdir`を指定してないことに気付いたら鋭いですね。
`autouse=True`を付けることで、テストメソッドで呼び出さなくてもfixtureを呼び出せます。


## 実例: fixtureを使ったディレクトリ作成②　階層的なfixtureの呼び出しとbuilt-in fixture

もう少し複雑な例として、上記の実例を修正を加え、テストメソッドの属するクラスごとに更にディレクトリを作成してみます。
下記のディレクトリ構成を想定し、テスト実行時に`outputs/{%y%m%d-%H%M%S}/`以下に、
実行されたテストクラスのディレクトリを生成します。

```shell
.
    tests/
        outputs/
            {%y%m%d-%H%M%S}/
                TestMakeDir  # 今回の作成対象ディレクトリ
                TestMakeDirTwo  # 今回の作成対象ディレクトリ
        conftest.py
        test_makedir.py
````

この場合、`conftest.py`は以下のようになります。

```conftest.py
import os
from datetime import datetime
 
import pytest
 
PATH_OUTPUTS = "./tests/outputs"
 
 @pytest.fixture(scope="session", autouse=True)
 def parentdir():
     name_parentdir = datetime.now().strftime("%y%m%d-%H%M%S")
     path_parentdir = os.path.join(PATH_OUTPUTS, name_parentdir)
     os.makedirs(path_parentdir)
     return path_parentdir

 @pytest.fixture(scope="class")
 def classdir(request, parentdir) -> str:
     path_classdir = os.path.join(parentdir, request.node.name)
     os.makedirs(path_classdir)
     return path_classdir
```

```test_makedir.py
class TestMakeDir:
    def test_makedir(self, classdir): # 以下省略

class TestMakeDirTwo:
    def test_makedir_two(self, classdir): # 以下省略
```

まずfixture`classdir`の引数に`parentdir`が指定されていることに注目してください。
ここではfixture`parentdir`が返却する`path_parentdir`を変数`parentdir`に格納しています。

もう一つ、ficture`classdir`には`request`というimportもされていない謎の引数もありますね。
これはpytestにビルトインされているfixtureで、importなしで使えます。
`request.node.name`には呼び出したノードの名前、すなわち`TestMakeDir`や`TestMakeDirTwo`が格納されています。これで最終的な出力ディレクトリが生成できます。

https://docs.pytest.org/en/latest/reference/reference.html#std-fixture-request

`request`の他にも色々なbuilt-in fixtureがあるので、調べてみてください。

https://docs.pytest.org/en/latest/reference/fixtures.html#reference-fixtures

## まとめ

fixtureを使いこなすと夢が広がります。

## 参考

しっかり理解したい方は公式ドキュメントを参照ください。

https://docs.pytest.org/en/latest/explanation/fixtures.html?highlight=fixture