.. _topics-testing: ============================== Djangoアプリケーションのテスト ============================== :revision-up-to: 11321 (1.1) unfinished .. module:: django.test :synopsis: Django アプリケーション用のテストツールです。 今日の Web 開発者にとって、自動化されたテストはバグ潰しの工程で極めて有用な ツールです。複数のテストを集めた **テストスイート** を使えば、Web開発におけ るいくつもの問題を解決したり回避したりできます: * 新たなコードを書く際、コードが期待通りに動作するかテストできます。 * 以前のコードを修正したりリファクタする場合、テストによって、コードの変 更がアプリケーションに意図しない影響を及ぼさないか調べられます。 Web アプリケーションは、 HTTP レベルのリクエスト処理、フォームの検証と処理、 そしてテンプレートレンダリングまで、複数のロジックレイヤから構成されていま す。そのため、Webアプリケーションのテストは複雑なタスクです。 Django のテス ト実行フレームワークと関連ユーティリティを使えば、仮想的なリクエストを発行 したり、テストデータを挿入したり、アプリケーションの出力を調べて、コードが 期待通りに動作しているか検証したりできます。 しかも素晴らしいことに、テストはとても簡単なのです。 このドキュメントは前後半の 2 つの節に別れています。前半では Django における テストの書き方を、後半ではテストの実行方法を説明します。 テストを書く ============ Django でテストを書く方法は主に 2 つあり、それぞれ Python の標準ライブラリ についてくる二つのテストフレームワークに対応しています。フレームワークは以 下の 2 つです: * **doctest** -- 関数やクラスの docstring (ドキュメンテーション文字列) に埋め込まれたテストで、例えば以下のように Python の対話インタプリタ セッションを模した方法で書かれています:: def my_func(a_list, idx): """ >>> a = ['larry', 'curly', 'moe'] >>> my_func(a, 0) 'larry' >>> my_func(a, 1) 'curly' """ return a_list[idx] * **ユニットテスト (unit test)** -- 以下の例のように、テストを ``unittest.TestCase``` のサブクラスのメソッドとして表現したものです:: import unittest class MyFuncTestCase(unittest.TestCase): def testBasic(self): a = ['larry', 'curly', 'moe'] self.assertEquals(my_func(a, 0), 'larry') self.assertEquals(my_func(a, 1), 'curly') 好みに応じて、どちらのテストを使ってもかまいませんし、両方のテストを組み合 わせてもかまいません。また、後でほんの少しだけ説明しますが、他のテストフレー ムワークを使っても構いません。 doctest を書く -------------- doctest は、Python の標準モジュール、 doctest_ によるテストです。 doctest は docstring (ドキュメンテーション文字列) から、 Python の対話インタプリタ セッションの形式をとる部分を探し出して実行します。 doctest の詳しい構造につ いての解説はこのドキュメントの範囲を超えた話なので、公式ドキュメントを参照 してください。 .. admonition:: **docstring** とは? docstring の詳しい説明 (と、効果的な docstring の書き方) は、 :pep:`257` (訳注: PEP 257 の和訳は http://www.python.jp/doc/contrib/peps/pep-0257.txt にあります) に書かれ ています: docstring とは、モジュールや関数、クラス、メソッド定義のブロック中 の先頭に置かれた文字列リテラルです。 docstring を定義すると、オブジェ クトの ``__doc__`` という特殊な属性になります。 例えば、下の関数には、関数の説明の入った docstring があります:: def add_two(num): "引数に指定した数に 2 を加えて返します。" return num + 2 テストはそれ自体素晴らしいドキュメントになることも多いので、テストをそ のまま docstring に入れておけば、ドキュメント化とコードのテストの *両方を* 効率的に行えます。 テストランナは、 Django アプリケーションの中の以下のファイルから doctest を 探して実行します: * ``models.py`` ファイル。モジュールレベル、かつ/またはモデルレベルの doctest を記述します。一般には、アプリケーションレベルの doctest はモ ジュールの docstring として記述し、モデルレベルの docstring はモデル クラスの docstring として記述します。 * アプリケーションディレクトリ、すなわち ``models.py`` の入ったディレク トリ下に置かれた ``tests.py`` という名前のファイル。このファイルは、 モデルに関係しないような doctest を書きたい場合のフックとして使えます。 モデル定義で doctest を使った例を示します:: # models.py from django.db import models class Animal(models.Model): """ 鳴き声から動物を調べるテスト # 動物インスタンスを生成する >>> lion = Animal.objects.create(name="lion", sound="roar") >>> cat = Animal.objects.create(name="cat", sound="meow") # speak() を呼び出す >>> lion.speak() 'The lion says "roar"' >>> cat.speak() 'The cat says "meow"' """ name = models.CharField(max_length=20) sound = models.CharField(max_length=20) def speak(self): return 'The %s says "%s"' % (self.name, self.sound) :ref:`テストを実行する ` と、テストユーティリティは上の docstring を探し出して、Python の対話セッションに見える部分を検出し、そこに 書かれた命令を実行して、docstring に書かれた結果と実際の実行結果が一致する かどうかを確かめます。 モデルテストの場合、テストランナが独自にテストデータベースを作成します。す なわち、データベースに対してアクセスするテスト -- 例えば、モデルインスタン スを生成して保存するようなテスト -- が、実運用のためのデータベースに影響を 及ぼすことはありません。 doctest はいずれも「白紙状態」、すなわち、各モデル のデータベーステーブルが空の状態で実行されます (詳しくは、後述のフィクスチャ の節を参照してください) 。この機能を使うには、 Django がデータベースに接続 するときのユーザに ``CREATE DATABASE`` の権限を持たせておかねばならないので 注意してください。 doctest の詳しい動作原理は、 `標準ドキュメントの doctest の項`_ を参照してください。 .. doctest: http://python.jp/doc/release/lib/module-doctest.html .. _doctest: http://python.jp/doc/release/lib/module-doctest.html .. _standard library documentation for doctest: doctest_ .. _`標準ドキュメントの doctest の項`: doctest_ ユニットテストを書く -------------------- Django の単体テストもまた、doctest と同様、標準ライブラリモジュールの unittest_ を使います。このモジュールは、 doctest_ とは違った、 クラスベース のやり方でテストを定義します。 doctest と同様、 Django のテストランナは、以下の二つの場所からユニットテス トを探します: * ``models.py`` ファイル。テストランナはこのモジュールから ``unittest.TestCase`` のサブクラスを探します。 * アプリケーションディレクトリ、すなわち ``models.py`` の入ったディレク トリ下に置かれた ``tests.py`` という名前のファイル。上と同様に、テス トランナはこのモジュールから ``unittest.TestCase`` のサブクラスを探し ます。 以下の ``unittest.TestCase`` のサブクラスは、前節の doctest と同じテストを 実現しています:: import unittest from myapp.models import Animal class AnimalTestCase(unittest.TestCase): def setUp(self): self.lion = Animal.objects.create(name="lion", sound="roar") self.cat = Animal.objects.create(name="cat", sound="meow") def testSpeaking(self): self.assertEquals(self.lion.speak(), 'The lion says "roar"') self.assertEquals(self.cat.speak(), 'The cat says "meow"') :ref:`テストを実行する ` と、テストユーティリティはデフォル トの動作として、 ``models.py`` と ``tests.py`` に入っている全てのテストケー ス (つまり ``unittest.TestCase`` のサブクラス) を捜し出し、そこからテストス イートを自動的に構築して、スイートを実行します。 開発バージョンの Django には、あるモジュールのテストスイートを定義する方法 をもう一つ提供しています: ``models.py`` や ``tests.py`` で ``suite()`` メ ソッドを定義している場合、 Django のテストランナはこのメソッドを使ってテス トスイートを構築します。この仕様は、ユニットテストにおいて `おすすめのテストスイート構築方法`_ に従っています。複雑なテストスイー トの構築方法についての詳細は Python のドキュメントを参照してください。 ``unittest`` の詳細は `標準ライブラリドキュメントの unittest の項`_ を参照 してください。 .. unittest: http://docs.python.org/lib/module-unittest.html .. _unittest: http://python.jp/doc/release/lib/module-unittest.html .. _standard library unittest documentation: unittest_ .. suggested organization: http://docs.python.org/lib/organizing-tests.html .. _suggested organization: http://python.jp/doc/release/lib/organizing-tests.html .. _`標準ライブラリドキュメントの unittest の項`: unittest_ .. _`おすすめのテストスイート構築方法`: `suggested organization`_ どちらのテストを使うべきか -------------------------- Django は標準的な Python テストフレームワークの両方をサポートしているので、 どちらを選ぶかは、開発者個々人の好み次第です。もちろん、 *両方* を同時に使っ てもかまいません。 とはいえ、テストを初めて書く開発者にとっては、この選択は混乱 のもとになるでしょう。そこで、どちらのテストを使うべきかを決める手がかりに なるよう、 doctest と単体テストの主な違いについて示します: * そこそこ Python に慣れているなら、 ``doctest`` の方がより「Python的 (pythonic)」に感じることでしょう。 doctest はテストをできるだけ楽に書け るように設計されているので、クラスやメソッドを書くときのオーバヘッドが ほとんどありません。単に docstring にテストを書くだけでよいのです。それ に、モジュールに対してドキュメントを自動生成させられるという利点もあり ます。つまり、 doctest を上手に書けば、ドキュメント作成とテストを同時に 片付けられて一石二鳥、というわけです。 また、テストにまだ慣れていない開発者は、 doctest を使った方が早くテスト について学べることでしょう。 * Java での開発経験のある開発者なら、 ``unittest`` フレームワークはとても 理解しやすいはずです。 ``unittest`` は Java の JUnit に影響を受けている ので、他の言語で JUnit から派生したテストフレームワークを使ったことがあ るのなら、``unittest`` はかなりしっくりくるはずです。 * 同じコードを含むような一連のテストを書くときには、 ``unittest`` フレー ムワークのクラスとメソッドによる設計が気に入るでしょう。共通のタスクを 抽象化して、メソッド化できるからです。また、 ``unittest`` フレームワー クは ``unittest`` はテストの初期化/終了処理のルーチンをサポートしてい るので、テストケースを実行するときの環境を高い水準でコントロールできま す。 繰り返しになりますが、(一つのアプリケーションの中であっても) 両方のシステム を並べて使えることを忘れないでください。どちらのテストシステムにも、状況に 応じて適した部分があるので、大抵のプロジェクトでは、最終的には両方のテスト システムを使うことになるものです。 .. _running-tests: テストを実行する ================ テストを実行するには、プロジェクトの ``manage.py`` ユーティリティを使います:: $ ./manage.py test 特定のアプリケーションに対してテストを実行したければ、コマンドラインにアプ リケーションの名前を追加します。例えば、 :setting:`INSTALLED_APPS` に ``myproject.polls`` と ``myproject.animals`` というアプリケーションが入って おり、 animals の単体テストを実行したいだけなら、以下のようにします:: $ ./manage.py test animals ``animals`` ではなく、 ``myproject.animals`` なので注意してください。 .. versionadded:: 1.0 実行したいテストを選べるようになりました。 単体テストを実行する際、どのテストを実行するかを指定できます。あるアプリケー ション用のテストケースを実行する場合 (例えば、上で解説した AnimalTestCase の場合) は、コマンドラインにテストケースの名前を追加してください:: $ ./manage.py test animals.AnimalTestCase テストケース中の *個別の* テストメソッドを実行したければ、メソッド名を指定して ください:: $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals テストデータベース ------------------- データベースを必要とするテスト (モデルのテスト) に、「本番の (実運用環境の)」 データベースは必要ありません。テストの際には、空の別のデータベースが作成さ れます。 テストがパスするか失敗するかに関わらず、テストデータベースは全てのテストを 実行し終えると消去されます。 デフォルトでは、テストデータベースの名前は、 :setting:`DATABASE_NAME` に指 定したデータベース名の前に ``test_`` を付けたものです。 SQLite データベース エンジンを使っている場合、デフォルトではテストをメモリ上のデータベースで行 います(すなわち、データベースをメモリ上に生成し、ファイルシステムを全く経由 しません!) 。テストデータベースの名前をデフォルト意外の値にしたければ、 :setting:`TEST_DATABASE_NAME` 設定を使って指定します。 テスト用に別のデータベースを使うことを除けば、テストランナは設定ファイルの データベースに関する他の設定、 :setting:`DATABASE_ENGINE`, :setting:`DATABASE_USER`, :setting:`DATABASE_HOST` などをそのまま使います。 テストデータベースは :setting:`DATABASE_USER` の権限で作成されるので、この ユーザは新たに生成されたデータベースを操作する権限を備えていなければなりま せん。 .. versionadded:: 1.0 テスト用データベースの文字セットエンコーディング設定を細かく調整したいのな ら、 :setting:`TEST_DATABASE_CHARSET` 設定を使ってください。 MySQL を使って いるなら、 :setting:`TEST_DATABASE_COLLATION` でテストデータベースで使うコ レーション(collation) を指定できます。これらの設定については、 :ref:`settings ファイルのドキュメント` を参照してください。 その他のテスト条件 --------------------- 設定ファイル上の :setting:`DEBUG` の値にかかわらず、 Django は全てのテスト を :setting:`DEBUG=False` で動かします。これは、テストコードに実運用環境と 同じ内容を出力させるためです。 テストプログラムの出力を理解する --------------------------------- テストを実行すると、テストランナ自体が大量に出力するメッセージに出くわすで しょう。コマンドラインの ``verbosity`` オプションを使えば、メッセージの詳細 レベルを制御できます:: Creating test database... Creating table myapp_animal Creating table myapp_mineral Loading 'initial_data' fixtures... No fixtures found. 前の節でも述べたように、このメッセージは、テストランナがテストデータベース を作成したことを示しています。 テストデータベースが生成されると、 Django はテストを実行します。全てのテス トにパスすると、最後には以下のようなメッセージが表示されます:: ---------------------------------------------------------------------- Ran 22 tests in 0.221s OK 一方、失敗したテストがあると、失敗したテストに関する詳しい情報が表示されま す:: ====================================================================== FAIL: Doctest: ellington.core.throttle.models ---------------------------------------------------------------------- Traceback (most recent call last): File "/dev/django/test/doctest.py", line 2153, in runTest raise self.failureException(self.format_failure(new.getvalue())) AssertionError: Failed doctest test for myapp.models File "/dev/myapp/models.py", line 0, in models ---------------------------------------------------------------------- File "/dev/myapp/models.py", line 14, in myapp.models Failed example: throttle.check("actor A", "action one", limit=2, hours=1) Expected: True Got: False ---------------------------------------------------------------------- Ran 2 tests in 0.048s FAILED (failures=1) エラー出力の詳細はこのドキュメントの範囲を超えるので解説はしませんが、ほと んど直感的に理解できる内容のはずです。詳しくは、 Python の ``unittest`` ラ イブラリのドキュメントを参照してください。 スクリプトのリターンコードは失敗したテストや出力のおかしかったテストの総数で す。全てのテストにパスしていれば、リターンコードは 0 です。この仕様は、テス トランナをシェルスクリプト上で動かしたり、テストが成功したかどうかをテスト ランナのレベルで調べたい場合に便利です。 テスト用のツール ================ Django は、テストを書くときに便利なツールをいくつか提供しています。 テストクライアント ------------------ .. module:: django.test.client :synopsis: Django のテストクライアントです。 テストクライアント (test client) は、簡単なダミーブラウザとして動作する Python のクラスです。テストクライアントを使うと、ビューをテストしたり、 プログラムを使ってDjango で作られたアプリケーションとやりとりできます。 テストクライアントを使ってできることをいくつか挙げましょう: * ある URL に対する GET や POST をシミュレートでき、低水準の HTTP (レスポ ンスヘッダや状態コード) 情報から、ページの内容まで、リクエストに対するレ スポンスの全てを調べられます。 * 特定の URL に対して正しいビューが呼び出されるかどうかを調べられます。 * 特定のリクエストに対して、特定のテンプレートを使ったレンダリングが行わ れ、その際に特定の値が入ったコンテキストが使われているかどうかを調べら れます。 テストクライアントは Twill_ や Selenium_ やその他のブラウザ自動化フレームワー クを置き換えようとするものではありません。 Django のテストクライアントはもっ と別の部分に焦点を当てているのです。すなわち: * 正しいビューが呼び出され、ビューが正しいコンテキストデータを生成してい るかどうかは、 Django のテストクライアントを使って調べてください。 * Twill や Selenium は、 *レンダリング済みの* HTML や、 JavaScript の機能 のような Web ページの *ビヘイビア* のテストに使ってください。 網羅的なテストスイートでは、両方のタイプのテストを組み合わせて使うはずです。 .. _Twill: http://twill.idyll.org/ .. _Selenium: http://www.openqa.org/selenium/ テストの概要と簡単な例 ~~~~~~~~~~~~~~~~~~~~~~ テストクライアントを使うには、 ``django.test.client.Client`` をインスタンス 化して、 Web ページを取得します:: >>> from django.test.client import Client >>> c = Client() >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'}) >>> response.status_code 200 >>> response = c.get('/customer/details/') >>> response.content '>> c.get('/login/') は正しいですが、次の呼び出し:: >>> c.get('http://www.example.com/login/') は正しくありません。 Django のプロジェクトから生成されていない Web ページは、テストクライ アントで取得できません。Django 以外の Web ページを取得したければ、 urllib_ や urllib2_ のような Python 標準ライブラリを使ってください。 * テストクライアントは URL の解決に :setting:`ROOT_URLCONF` に指定され た URLconf を使います。 * 上の例は Python の対話インタプリタ中でも動作しますが、一部のテンプレー ト関連の機能などは *テストの実行中だけ* でしか使えません。 というのも、 Django のテストランナは、あるビューでどのテンプレートが ロードされるかを決定するためにちょっとした黒魔術的なコードを使ってい るからです。この黒魔術 (実際には、メモリ上のテンプレートシステムに対 するパッチ) は、テスト実行時にしか適用されません。 .. _urllib: http://docs.python.org/lib/module-urllib.html .. _urllib2: http://docs.python.org/lib/module-urllib2.html リクエスト生成 ~~~~~~~~~~~~~~ リクエストの生成には、 ``django.test.client.Client`` クラスを使います。 ``Client`` は引数なしで生成します。: .. class:: Client() ``Client`` のインスタンスからは、以下のメソッドを呼び出せます: .. method:: Client.get(path, data={}, follow=False, **extra) ``path`` に対する GET リクエストを行い、 ``Response`` オブジェクト を返します。 ``Response`` オブジェクトについては後で説明します。 引数 ``data`` は辞書オブジェクトで、キー/値のペアが GET データのペ イロードの生成に使われます。例えば:: >>> c = Client() >>> c.get('/customers/details/', {'name':'fred', 'age':7}) は、以下のような GET リクエストの送信と同じです:: /customers/details/?name=fred&age=7 The ``extra`` keyword arguments parameter can be used to specify headers to be sent in the request. For example:: >>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, ... HTTP_X_REQUESTED_WITH='XMLHttpRequest') ...will send the HTTP header ``HTTP_X_REQUESTED_WITH`` to the details view, which is a good way to test code paths that use the :meth:`django.http.HttpRequest.is_ajax()` method. .. versionadded:: 1.1 If you already have the GET arguments in URL-encoded form, you can use that encoding instead of using the data argument. For example, the previous GET request could also be posed as:: >>> c = Client() >>> c.get('/customers/details/?name=fred&age=7') If you provide URL both an encoded GET data and a data argument, the data argument will take precedence. If you set ``follow`` to ``True`` the client will follow any redirects and a ``redirect_chain`` attribute will be set in the response object containing tuples of the intermediate urls and status codes. If you had an url ``/redirect_me/`` that redirected to ``/next/``, that redirected to ``/final/``, this is what you'd see:: >>> response = c.get('/redirect_me/', follow=True) >>> response.redirect_chain [(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)] .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra) ``path`` に対する POST リクエストを行い、 ``Response`` オブジェクト を返します。 引数 ``data`` は辞書オブジェクトで、キー/値のペアが POST データの ペイロード生成に使われます。例えば:: >>> c = Client() >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'}) は、以下のパス:: /login/ への POST リクエストで、以下の POST データ:: name=fred&passwd=secret を伴います。 ``content_type`` を指定した場合 (例えば XML ペイロードの場合には ``text/xml``)、引数 ``data`` の中身は POST リクエストそのままで送信 され、 ``Content-Type`` ヘッダに ``content_type`` の値を使います。 ``content_type`` を指定しなければ、 ``data`` の中身は ``multipart/form-data`` で送信されます。この場合、 ``data`` の中の キー/値のペアは、マルチパートメッセージにエンコードされ、 POST デー タのペイロード生成に使われます。 あるキーに対して複数の値を提出 (submit) する場合 (例えば、 ``