テストはテスト項目の洗い出しが一番大変な思い出が・・・
テスト項目さえしっかりしていれば、実際のテストはプログラム化できるとスマートだね。
flaskのチュートリアルで、シンプルなブログアプリflaskrを作成しています。「test coverage」という項目をやっていて、Pythonモジュールであるpytestの使い方がわからなかったので、前回までにpytestについて勉強をしていました。
ある程度Pytestについて理解したと感じましたので、引き続きチュートリアルを進めていきます。今回のテストの項目も長めなので、頑張っていきたいと思います。
pytestについては以下の記事で勉強しましたのでご参照ください。
こんな人の役に立つかも
・PythonでWebアプリケーションを作成したい人
・flaskのチュートリアルを行なっている人
・flaskのチュートリアルでブログアプリflaskrを作成している人
flaskアプリのテスト
プログラムのテストにはいろいろなやり方があると思いますが、基本的には、実際に操作を行ってみることだと思います。小さな規模だとそれでも良いかと思いますが、今回のチュートリアルでは、テストの部分もプログラム化するというやり方を学ぶことができます。
これから、たくさんのファイルが出てきて若干よくわからなくなります^^;ので、まずは、テストで利用する全体のファイル構成を見ていきたいと思います。
今回のチュートリアルで作成するテストのプログラムは、flask-tutorialフォルダ内の「tests」というフォルダに配置していきます。
pytestで勉強したように、fixtureのプログラムを「conftest.py」というファイルに記載し、テストプログラムで共有できるようにしておきます。
そして、「test_●●.py」に実際行いたいテストを記載していきます。
「setup.cfg」は、必須ではないとのことですが、pytestの探索するフォルダを絞ることができるようで、今回は「tests」フォルダにテストプログラムがありますよ、と指定するために利用しています。
conftest.pyも、test_とファイル名を付けることもpytestチュートリアルで勉強しました。
セットアップとFixture
「tests」というフォルダをflask-tutorialフォルダ内に作成します。
そして、以下の2つのプログラムを作成します。
1:「data.sql」
INSERT INTO user (username, password)
VALUES
('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'),
('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79');
INSERT INTO post (title, body, author_id, created)
VALUES
('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00');
テストで呼び出すSQL文を別のファイルとして呼び出すようにします。2つのINSERT文から成り立っています。
1つ目のSQL文では、「user」テーブルにVALUEより後ろの2つの「ユーザー名」と「パスワード」を挿入するようになっています。
2つ目の「post」テーブルには、VALUEの後の値をそれぞれ「title」「body」「author_id」「created」にいれています。(「||」は文字列の結合で、「x’0A’」はASCIIコードで改行を表すので、bodyには「test(改行)body」という文が入ります。)
念のため、定義したデータベーステーブルの図です。
2:conftest.py
pytestのチュートリアルでも勉強したconftest.pyを作成していきます。
作成しているflaskrアプリはimportできるようにしてありますので、このようにtestフォルダを並置してimportすることができるんですね。
import os
import tempfile
import pytest
from flaskr import create_app
from flaskr.db import get_db, init_db
#①先に作成したdata.sqlを読み込む。
with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f:
_data_sql = f.read().decode('utf8')
#②アプリケーションファクトリのfixture
@pytest.fixture
def app():
#DB用の一時ファイルを作成
db_fd, db_path = tempfile.mkstemp()
app = create_app({
'TESTING': True,
'DATABASE': db_path,
})
#データベースの初期化と、スクリプトの実行
with app.app_context():
init_db()
get_db().executescript(_data_sql)
#appをここで返す
yield app
os.close(db_fd)
os.unlink(db_path)
#③テスト用クライアント
@pytest.fixture
def client(app):
return app.test_client()
#④テスト用ランナー
@pytest.fixture
def runner(app):
return app.test_cli_runner()
①では、先ほど作成したdata.sqlを読みこんでいます。pythonではwith文を利用すると、close処理など意識しなくて良い点が便利ですね。一般的なファイル読み込みの形です。
②アプリケーションファクトリのfixture
②では「@pytest.fixture」でappというfixtureを作成しています。アプリケーションファクトリを作成するfixtureになっています。fixtureはテストで共有できるプログラムの部品というイメージです。
「tempfile.mkstemp() 」で、テストで一時的に利用するための「一時フォルダ」を作成して、pathを取得しておきます。本番環境ではSQLiteのフォルダが「instance」に作成されますが、本番と分離するために、一時フォルダにデータベースを作成するようにします。
そして、アプリケーションファクトリのcreate_appを呼び出します。以前、「__init__.py」にcreate_appをプログラムしたとき、test_configという引数を受けるようにしています。test_configの内容が存在するかどうかでテスト用とする条件を設けました。「test_config」はcreate_appの中で「app.config.create_mapping(test_config)」として利用されます。「TESTING」をTrue、「DATBASE」にテスト用データベースのパス(db_path)を入れることで、flaskのテスト環境でアプリケーションを稼働させるような設定となります。
データベースの初期化は「with app.app_context():」の構文にて実行されます。init_dbが行われた後、テスト用のSQL文(data.sql)を読みこんだ「__data_sql」を実行しています。
(アプリケーションコンテキストについては、いまいち理解していないのですが、appが開始して終了するまで、のような感覚でいます。)
appのfixtureでは、最後に、yieldでappを返しています。その後に続くデータベース終了処理は、appが終了するときに呼び出されるようになっています。(pythonのyield文)
③と④はテスト用のクライアントとランナー
テスト用のクライアントとランナーを準備するとのことです。
「client」fixtureは、Webアプリを操作するブラウザにあたるもので、「test_client」を利用することで、Webサーバーの稼働なしにアプリケーションにリクエストが送信できるそうです。
「runner」fixtureは、クリックコマンドを呼び出すことがで切るそうです。「test_cli_runner()」という関数を利用しています。
ここで定義した2つのfixtureは「app」fixtureを利用しています。
clientとrunnnerは実際に使ってみないとまだピンとこないです。
ここで、conftest.pyに定義したfixtureは今後テストで何回も利用することになります。やっとpytestのfixtureの存在意義が少しづつわかってきたような気がします。