Flaskアプリケーションのテスト

テストされていないアプリケーションは壊れています.

テストされていないアプリケーションは仕様が確認できませんので、完全に正しいとも、真実から遠いともいえません。 テストされていないアプリケーションに対して、既存のコードを改良するのは困難であり、そのアプリケーションの開発者はかなり偏執的になる傾向があります。 もし、アプリケーションテストを自動化したならば、安全に変更することができ、あなたの変更が何を壊したのかを即座に知ることができるようになります。

Flaskにはアプリケーションをテストする方法が複数あります。 主にWerkzeugテスト用の Client クラスをコード上に公開し、コンテキストローカルに扱います。 そして、あなたのお気に入りのテストソリューションで使うことができます。 このドキュメントでは、 Pythonのインストール時に組み込まれる、 unittest パッケージを使います。

アプリケーション

まず初めに、アプリケーションの機能をテストすることが必要です。 テストのために、 チュートリアル のアプリケーションを使います。 まだアプリケーションが手元にないならば、 からソースを手に入れてください。

テストのひな形

テスト用に第2のモジュール(flaskr_tests.py)を加え、ユニットテスト用のひな形を作ります.

import os
import flaskr
import unittest
import tempfile

class FlaskrTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, flaskr.DATABASE = tempfile.mkstemp()
        self.app = flaskr.app.test_client()
        flaskr.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(flaskr.DATABASE)

if __name__ == '__main__':
    unittest.main()

setUp() メソッドのコードは、新しいテストクライアントを作成し、新しいデータベースを初期化します。 この関数は個々のテスト関数から呼ばれます。 テストの後にデータベースを削除する時には、 tearDown() メソッドでファイルを閉じ、ファイルシステムから取り除きます。 テストクライアントは、アプリケーションへの簡単なインタフェースを提供します。 アプリケーションへのテストリクエストの引き金となることができ、クライアントはクッキーを保持します。

SQLite3がファイルシステムベースであるので、私たちは一時的なデータベースを作成して、初期化するのにtempfileモジュールを簡単に使用できます。 mkstemp() 関数は2つのことをします: 低レベルなファイルハンドルとデータベース名として使うランダムなファイル名を返します。 ファイルを閉じるのに os.close() 関数を使用できるように、 db_fd を置かなければなりません。

テストスイートを実行すると、以下の出力を見ることが出来ます:

$ python flaskr_tests.py

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

一つのテストも実行しませんでしたが、flaskrアプリケーションは文法上有効であることがわかりました。 そうでなければ、importで例外を出して失敗していたことでしょう。

最初のテスト

最初のテストを追加します。 アプリケーションのルート(/)にアクセスすると、アプリケーションが ”No entries here so far” を表示することをチェックしましょう。

class FlaskrTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
        self.app = flaskr.app.test_client()
        flaskr.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(flaskr.DATABASE)

    def test_empty_db(self):
        rv = self.app.get('/')
        assert 'No entries here so far' in rv.data

テスト関数の名前は test という言葉で始めます。このように名前をつけると自動的にテストの対象になります。 self.app.getを使うことで、特定のパスがあるアプリケーションにHTTP GETリクエストを送ることができます。 返り値は response_class オブジェクトになるでしょう。 アプリケーションから(文字列としての)返り値を点検するのに data 属性を使用できます。 この場合、 'No entries here so far' が出力の一部であることを保証します。

もう一度実行して、テストがパスするのを見てみましょう。

$ python flaskr_tests.py
.
----------------------------------------------------------------------
Ran 1 test in 0.034s

OK

もちろん、テストクライアントでフォームをsubmitでき、ユーザのログインにテストクライアントを使うことができます。

ログインとログアウト

アプリケーションの機能の大部分は管理ユーザのみが利用でき、そのため、テストクラインとからアプリケーションへログイン、ログアウトする方法が必要です。 そのために、必要なフォームのデータ(ユーザネームとパスワード)と一緒にログイン/ログアウトページへリクエストを送ります。 ログイン/ログアウトページはリダイレクトするため、 follow_redirects をクライアントに設定します。

下の2メソッドを FlaskrTestCase クラスに加えます。

def login(self, username, password):
    return self.app.post('/login', data=dict(
        username=username,
        password=password
    ), follow_redirects=True)

def logout(self):
    return self.app.get('/logout', follow_redirects=True)

ログインとログアウトの動作と無効な情報でログインに失敗することを簡単にテストすることができます。 この新しいテストをクラスに追加してください。

def test_login_logout(self):
    rv = self.login('admin', 'default')
    assert 'You were logged in' in rv.data
    rv = self.logout()
    assert 'You were logged out' in rv.data
    rv = self.login('adminx', 'default')
    assert 'Invalid username' in rv.data
    rv = self.login('admin', 'defaultx')
    assert 'Invalid password' in rv.data

メッセージを加えるテスト

メッセージを追加する動作をテストすることが出来ます。このように新しいテストを追加します。

def test_messages(self):
    self.login('admin', 'default')
    rv = self.app.post('/add', data=dict(
        title='<Hello>',
        text='<strong>HTML</strong> allowed here'
    ), follow_redirects=True)
    assert 'No entries here so far' not in rv.data
    assert '&lt;Hello&gt' in rv.data
    assert '<strong>HTML</strong> allowed here' in rv.data

ここでは、HTMLはテキストを許可し、タイトルを許可しないことをチェックします。これは、意図している振舞いです。

実行すると、3つのテストに合格します。

$ python flaskr_tests.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.332s

OK

ヘッダーとステータスコードによるより複雑なテストは、 MiniTwit Example のソースを調べてください。 それは、より大きなテストスイートを含んでいます。

その他のテストトリック

上で使ったテストクライアント以外に、 with 文と組み合わせて一時リクエストコンテキストを起動する際に使える test_request_context() があります。 view関数の中のように request , g , session のオブジェクトにアクセスできます。 例をお見せします。

app = flask.Flask(__name__)

with app.test_request_context('/?name=Peter'):
    assert flask.request.path == '/'
    assert flask.request.args['name'] == 'Peter'

他の全てのコンテキスト内のオブジェクトは同じように使うことができます。

もし、異なった構成でアプリケーションをテストしたくて、そうする良い方法が見つからないならば、アプリケーションファクトリへの切り替えを考えてください(アプリケーションファクトリ を参照)。

コンテキストの保持

New in version 0.4.

時々、定期的な要求が引き金となって、追加内省が起こることができるように少しの間コンテキストを保持することが、役立つ場合があります。 Flask 0.4 から with ブロック付の test_client() を使うことで可能になりました。

app = flask.Flask(__name__)

with app.test_client() as c:
    rv = c.get('/?tequila=42')
    assert request.args['tequila'] == '42'

もし、 with ブロックなしで test_client() を使うならば、 assert はエラーとともにに失敗します。なぜなら 実際のリクエストの外で使用されるため request はもはや有効ではないからです。 しかしながら、 after_request() がその時すでに呼ばれていて、データベース接続とそれに関わる全てが多分既に終了されていることを覚えておいて下さい。