Python3 Flaskで開発環境とテスト環境のデータベースを自動で切り替える

FlaskMongoEngineとテスト環境について

環境

  • Python 3.6.1
  • Flask==0.12.2
  • flask-mongoengine==0.9.3

方針

  • ローカルでは、開発用とテスト用のそれぞれでデータベースを用意する
  • テストを行うために、何かを書き換えるなどによる手動でのデータベースを切り替える操作はしない

現状

関係するファイルのフォルダ構造は次のようになっている。

.
├── app
│   ├── __init__.py
│   ├── models.py
│   └── views.py
├── config.py
└── run.py

ローカルで開発用にサーバーを起動するときは、python run.pyを走らせる。 本番環境では、Heroku上で、gunicorn app:appを走らせている。

appモジュールの初期化の際に、app/__init__.pyが走るがその中で、

# 各種インポート
app = Flask(__name__)

# 開発用と本番用を区別してデータベースのURIなどを決定する
config.configure_app(app)

# 生成したappを使って、db, view, modelなどをセットアップする
db = MongoEngine(app)
from app import views

を行なっている。

現状の問題点

  • テスト用の設定は開発用の設定がロードされたあとで行われてしまう
  • テスト時にテスト用のデータベースに自動で切り替えられない

    from app import <something>を行なった時点で、 appモジュール内でapp変数が生成して、viewsなどでそれを参照しているため、 後からapp.config["DATABASE_URI"]を変更して、 db = MongoEngine(app)を行なっても、正しく変更できない。 modelsのセットアップはすでに終わってしまっているため。

対策

  • appモジュール内のapp変数の設定を任意で設定できるようにする

具体的には、appモジュール内にapp変数生成用の関数を用意する。 その関数の引数で各環境の設定を分岐させる。

つまり、次のようなcreate_app関数を定義する。

# views, modelsで`from app import app`のままにするため、グローバルにする。
app = None
db = None

def create_app(testing=False):
    """
    Create app for dev or production or testing environment.
    """
    # グローバル変数へ代入し、参照できるようにする。
    global app, db
    app = Flask(__name__)
    config.configure_app(app, testing=testing)
    db = MongoEngine(app)

    # viewsでは、app変数が必要なので、このタイミングでimportする。
    from app import views
    return app

続いて、各環境での実行部分を変更する。

開発環境

run.pyでは、from app import app; app.run(params)だったのを、次のようにする。 (本番環境のため後でもう少し変更する。)

from app import create_app
// ここでcreate_appを実行し、appを取得。同時にappモジュール内でapp変数への代入が行われる。
app = create_app()

app.run('127.0.0.1', 5000)

テスト環境

テスト環境では、テストのセットアップで、次のような準備をすれば良い。

# 一旦関数をインポート
from app import create_app

# テストとしてapp変数を生成し、定義する。
app = create_app(testing=True)

# 関数を実行すれば、appモジュール内で`None`だった変数に代入される。
# よって正常にインポートし、利用できるようになる。
from app import models, db

class SomeAppTestCase(unittest.TestCase):
    def setUp(self):
        self.app = app.test_client()

    def test_testing_environment(self):
        # テスト用データベースなら、modelsは空のはず
        assert len(models.SomeData.objects) == 0

本番環境

本番では、gunicorn app:appで起動していた。 このままでは、appモジュール内のapp変数はNoneのままとなってしまうので起動しない。

そこで、それまで開発用にしていたrun.pyを利用することにした。app.run()python run.pyで直接実行した時のみ、走らせるようにする。

from app import create_app
app = create_app()

# ここは`gunicorn`によって呼ばれないはず。
if __name__ == "__main__":
    app.run('127.0.0.1', 5000)

このようにして、起動をgunicorn run:appに変更すれば良い。 (まだHeroku上では試してないです。ローカルでgunicorn run:appした限りでは、正常動作しました。)

タイトルとURLをコピーしました