Flask
とMongoEngine
とテスト環境について
環境
- 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
した限りでは、正常動作しました。)
コメント