pytest fixtureでユニットテストの前処理をする

Pytestによるテスト実行前に「環境設定をテスト用に差し替えたいなあ...」と思うことがありました。(接続先データベース等)

pytestコマンドの実行前に手動で環境変数を書き換えることも可能ですが、煩わしいので自動化したいです。

そこで今回はpytest fixtureでテストの実行前に自動で環境変数を書き換えました。
pytest fixtureを使えばテストの実行前に特定の処理を自動で実行することができます。


### 環境

Python3.8.10
pytest
flask

### ソースコード

```py:__init__.py

from http.client import BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_FOUND
from dotenv import load_dotenv
from flask import (Flask)
from app.presentation.shared.exceptionhandler.notfoundexception import NotFoundException
from app.presentation.user.userview import UserView
from app.configration.database.initdb import init_db
from app.presentation.shared.exceptionhandler.badrequestexception import BadRequestException
from app.presentation.shared.exceptionhandler.internalservererrorexception import InternalServerErrorException

def create_app():
    
    load_dotenv()
    app = Flask(__name__, instance_relative_config=True)

    # 環境変数から設定を読み込み
    app.config.from_envvar('FLASK_CONFIG')
    # DB読み込み
    init_db(app)
    
    # エンドポイント設定
    app.register_blueprint(UserView.user, url_prefix='/api/v1/users/')
    
    # エラーハンドリング設定
    app.register_error_handler(NOT_FOUND, NotFoundException.response)
    app.register_error_handler(BAD_REQUEST, BadRequestException.response)
    app.register_error_handler(INTERNAL_SERVER_ERROR, InternalServerErrorException.response)
    return app 


app = create_app()
```
```py:dev.py

# 開発環境用のDB接続設定
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:Hogehoge@localhost/myapp?charset=utf8'
```

```py:test.py

# テスト用の環境設定
TESTING = True
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:Hogehoge@localhost/test_myapp?charset=utf8'
SQLALCHEMY_TRACK_MODIFICATIONS = False
```

開発環境では```myapp``` データベースに、テストでは```test_myapp``` データベースに接続します。

開発用サーバの起動時に、以下のスクリプトでFLASK_CONFIG環境変数のパスを指定します。

```bash:run_devserver.sh
#!/bin/bash

service mysql start
export FLASK_APP=app 
export FLASK_ENV=development
# 環境設定が定義されたファイルパスを指定
export FLASK_CONFIG=/usr/local/my_app/config/dev.py
flask run
```

開発環境を動かすだけなら十分ですが、今回はテスト用の環境を分けたいのでこれではいけません。
テスト実行の前に FLASK_CONFIGを書き換えるのもは面倒です。

この問題を解消するためにpytest fixtureを使います。

実際のコードがこちらです。

```py:conftest.py

import pytest
from app import create_app
import os

# テスト実行時に1回、自動で実行する
@pytest.fixture(scope="session" , autouse=True)
def client():
    # 環境変数FLASK_CONFIGにtest.pyのパスを指定する
    os.environ["FLASK_CONFIG"] = "/usr/local/my_app/config/test.py"
　　
    # アプリケーションオブジェクトを作成
    app = create_app()
　　
　　# アプリケーションオブジェクトをcontextに追加
    app.app_context().push()
```

```@pytest.fixture``` というデコレータをつけた関数がテストコードの前に実行されます。 また、引数により実行タイミングや自動実行の有無を設定することができます。

#### fixtureのスコープ

|scope|実行タイミング|
|-----|-------------|
|function|テスト関数毎に実行される(デフォルトの設定)|
|class|クラス毎に1回実行される|
|module|ファイル毎に1回実行される|
|package|パッケージ毎に1回実行される|
|session|テストセッション(pytestコマンド)毎に1回実行される|


scope引数を省略するとfunctionスコープが設定されます。
今回はテスト実行前に環境設定を変更するだけでよいので、スコープはsessionで問題ありません。

#### 自動実行設定

通常fixtureはテストメソッドの引数に渡して実行します。

```py
@pytest.fixture
def hoge():
  print("testtest")

# fixtureを引数に渡す
def test_hoge(hoge)
  print("fugafuga")

```

しかし今回作成したfixtureのclientメソッドは特定の関数の前に実行するものではありません。また、1番最初のテストメソッドが実行される前にclientメソッドが実行されてほしいので、特定のメソッドの引数に渡すのも不適切です。
(私はテストメソッドの実行順序が保証されているか理解できていません。)

テストメソッドの引数に渡さずにfixtureを実行するには第二引数に```autouse=True```
を指定します。

まとめると、conftest.pyのclientメソッドはテストセッションごとに1回自動で実行されます。

これでテスト実行時に環境設定をテスト用に差し替えることができるようになりました。

### まとめ

テストコードは油断すると可読性が低下しがちですが
pyest fixtureを使えばメンテナンスしやすい状態にできそうです。