Pylons は、 Web アプリケーションに対するリクエストをエミュレートするた めに webtest を使用している Web ア プリケーションに強力なユニットテストの機能を提供します。そして、レスポ ンスが適切に扱われ、コントローラーが適切に値を処理していることを保障す ることができます。
Web アプリケーションのテストスイートを走らせるために、 Pylons は nose というテスト実行 /発見パッケージを利用しています。プロジェクトディレクトリにおいて nosetests を実行すると、 tests ディレクトリで作成した全てのテストが 実行されます。システム上に nose がインストールされていない場合は、以下 のコードを用いて setuptools でインストール可能です。
$ easy_install -U nose
開発用の設定と衝突するのを避けるために、テストは実行するときに test.ini 設定ファイルを使用します。 これは、 test.ini ファイルでデー タベースなどの設定をしなければ、テストはデータベース設定を見つけられな い ということを意味します。
Warning
nose が doctest を検索している際にエラーが起きる場合があります。こ れは、アプリケーションがロードされる 前に 、nose がモジュールを一 度にインポートしようとするためです。このため、アプリケーションが起 動していることに依存している moduls/ 以下のファイルが失敗します。
Pylons 0.9.6.1 以降は nose の plugin を含んでいます。それは doctest が モジュールをスキャンする前にアプリケーションをロードするためのもので、 モデルに対して doctest を行うことを可能にします。コマンドラインから nose と共にこのオプションを使用できます:
nosetests --with-pylons=test.ini
または setup.cfg で [nosetests] ブロックをセットアップすることによっ て:
[nosetests]
verbose=True
verbosity=2
with-pylons=test.ini
detailed-errors=1
with-doctest=True
テストを実行するには以下を実行します:
python setup.py nosetests
まず、下の例のように新しいプロジェクトとコントローラーを作成しましょう。
$ paster create -t pylons TestExample
$ cd TestExample
$ paster controller comments
コントローラーを作成すると、 2 つのファイルが生成されるのがわかるでしょ う。スタブ・コントローラーと、 testexample/tests/functional/ 以下の テストです。
testexample/controllers/comments.py ファイルを以下のように修正して ください。
from testexample.lib.base import *
class CommentsController(BaseController):
def index(self):
return 'Basic output'
def sess(self):
session['name'] = 'Joe Smith'
session.save()
return 'Saved a session'
そうしたら、コントローラーのアクションが適切に機能していることを確かめ るために基本的なテストを書きましょう。 testexample/tests/functional/test_comments.py を以下のように編集し てください。
from testexample.tests import *
class TestCommentsController(TestController):
def test_index(self):
response = self.app.get(url(controller='/comments'))
assert 'Basic output' in response
def test_sess(self):
response = self.app.get(url(controller='/comments', action='sess'))
assert response.session['name'] == 'Joe Smith'
assert 'Saved a session' in response
メインプロジェクトのディレクトリの中で nosetests を実行すると、すべ てのテストが通ることが確認できるはずです。
..
----------------------------------------------------------------------
Ran 2 tests in 2.999s
OK
残念ながら、 通常の assert 文は、第 2 引数を指定しない限り、失敗したア サーションの結果に関する詳細な情報を提供しません。例えば、 test_sess 関数に以下のテストを追加してみてください。
assert response.session.has_key('address') == True
nosetests を実行すると、以下のようなあまり親切でない結果が返ってき ます:
.F
======================================================================
FAIL: test_sess (testexample.tests.functional.test_comments.TestCommentsController)
----------------------------------------------------------------------
Traceback (most recent call last):
File "~/TestExample/testexample/tests/functional/test_comments.py", line 12, in test_sess
assert response.session.has_key('address') == True
AssertionError:
----------------------------------------------------------------------
Ran 2 tests in 1.417s
FAILED (failures=1)
次のようにすることで、この結果を変えることができます:
assert response.session.has_key('address') == True, "address not found in session"
その結果はこうなります:
.F
======================================================================
FAIL: test_sess (testexample.tests.functional.test_comments.TestCommentsController)
----------------------------------------------------------------------
Traceback (most recent call last):
File "~/TestExample/testexample/tests/functional/test_comments.py", line 12, in test_sess
assert response.session.has_key('address') == True
AssertionError: address not found in session
----------------------------------------------------------------------
Ran 2 tests in 1.417s
FAILED (failures=1)
しかし、すべての assert 文でこのようなことをしなければならないのは時間 の無駄というものです。 TestController は Python 標準の unittest.TestCase クラスのサブクラスなので、より詳細な AssertionError を自動的に提供する assertEqual などのヘルパーメソッ ドを使うことができます。新しいテストコードは以下のようになります。
self.assertEqual(response.session.has_key('address'), True)
これは、より有用な失敗メッセージを出力します:
.F
======================================================================
FAIL: test_sess (testexample.tests.functional.test_comments.TestCommentsController)
----------------------------------------------------------------------
Traceback (most recent call last):
File "~/TestExample/testexample/tests/functional/test_comments.py", line 12, in test_sess
self.assertEqual(response.session.has_key('address'), True)
AssertionError: False != True
Pylons は webtest の webtest.TestResponse オブジェクト に属性をいくつか追加するので、その属性を通して Web リクエストの間に生成 された様々な変数にアクセスできます。
session
セッションオブジェクト
req
リクエストオブジェクト
c
テンプレートに渡される変数を含んだオブジェクト
g
グローバルオブジェクト
これらの変数を使うには、単に get/post コマンドを利用した 後で response の属性にアクセスします。
response = app.get('/some/url')
assert response.session['var'] == 4
assert 'REQUEST_METHOD' in response.req.environ
Note
response オブジェクトはすでに TestRequest オブジェクトを持っているため、 Pylons は request オ ブジェクトを response の req 属性として割り当てています。
WebTest の fixture テストでは、テストの中でアクセスしたい独自オブジェク トを指定することができます。この強力な機能のおかげで、 1 回のリクエスト の間だけ保持されているようなオブジェクトの値のテストを簡単に行うことが できます。
テストのためにオブジェクトを使えるようにする前に、アプリケーションがい つテストされるかを知ることは役に立ちます。 WebTest は paste.testing という環境変数を提供しており、その存在や真偽を確認す ることで必要な時だけアプリケーションがテストオブジェクトを投入すること ができます。
webtest のレスポンスオブジェクトへの独自オブジェクトの投入は、独 自オブジェクトを environ 辞書の paste.testing_variables キーに追加 することによって行います。 Pylons はこの辞書をアプリケーションが呼ばれ る前に作成するので、存在を確かめてから新しい値を追加することが推奨され ます。 paste.testing_variables 辞書に割り当てられているすべての変数 は、そのキーを属性名にすることで、レスポンスオブジェクトで利用可能です。
Note
WebTest は paste.fixture と呼ばれる Paste コンポーネントから抽出さ れたスタンドアロンバージョンです。 後方互換性のため、 WebTest は environ の paste.testing_variables キーを尊重し続けています。
例:
# testexample/lib/base.py
from pylons import request
from pylons.controllers import WSGIController
from pylons.templating import render_mako as render
class BaseController(WSGIController):
def __call__(self, environ, start_response):
# Create a custom email object
email = MyCustomEmailObj()
email.name = 'Fred Smith'
if 'paste.testing_variables' in request.environ:
request.environ['paste.testing_variables']['email'] = email
return WSGIController.__call__(self, environ, start_response)
# testexample/tests/functional/test_controller.py
from testexample.tests import *
class TestCommentsController(TestController):
def test_index(self):
response = self.app.get(url(controller='/'))
assert response.email.name == 'Fred Smith'
See also
XXX: Describe unit testing an applications models, libraries
XXX: Describe functional/integrated testing, WebTest