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

revision-up-to:11321 (1.1) unfinished

今日の 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 の詳しい構造につ いての解説はこのドキュメントの範囲を超えた話なので、公式ドキュメントを参照 してください。

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)

テストを実行する と、テストユーティリティは上の docstring を探し出して、Python の対話セッションに見える部分を検出し、そこに 書かれた命令を実行して、docstring に書かれた結果と実際の実行結果が一致する かどうかを確かめます。

モデルテストの場合、テストランナが独自にテストデータベースを作成します。す なわち、データベースに対してアクセスするテスト – 例えば、モデルインスタン スを生成して保存するようなテスト – が、実運用のためのデータベースに影響を 及ぼすことはありません。 doctest はいずれも「白紙状態」、すなわち、各モデル のデータベーステーブルが空の状態で実行されます (詳しくは、後述のフィクスチャ の節を参照してください) 。この機能を使うには、 Django がデータベースに接続 するときのユーザに CREATE DATABASE の権限を持たせておかねばならないので 注意してください。

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"')

テストを実行する と、テストユーティリティはデフォル トの動作として、 models.pytests.py に入っている全てのテストケー ス (つまり unittest.TestCase のサブクラス) を捜し出し、そこからテストス イートを自動的に構築して、スイートを実行します。

開発バージョンの Django には、あるモジュールのテストスイートを定義する方法 をもう一つ提供しています: models.pytests.pysuite() メ ソッドを定義している場合、 Django のテストランナはこのメソッドを使ってテス トスイートを構築します。この仕様は、ユニットテストにおいて おすすめのテストスイート構築方法 に従っています。複雑なテストスイー トの構築方法についての詳細は Python のドキュメントを参照してください。

unittest の詳細は 標準ライブラリドキュメントの unittest の項 を参照 してください。

どちらのテストを使うべきか

Django は標準的な Python テストフレームワークの両方をサポートしているので、 どちらを選ぶかは、開発者個々人の好み次第です。もちろん、 両方 を同時に使っ てもかまいません。

とはいえ、テストを初めて書く開発者にとっては、この選択は混乱 のもとになるでしょう。そこで、どちらのテストを使うべきかを決める手がかりに なるよう、 doctest と単体テストの主な違いについて示します:

  • そこそこ Python に慣れているなら、 doctest の方がより「Python的 (pythonic)」に感じることでしょう。 doctest はテストをできるだけ楽に書け るように設計されているので、クラスやメソッドを書くときのオーバヘッドが ほとんどありません。単に docstring にテストを書くだけでよいのです。それ に、モジュールに対してドキュメントを自動生成させられるという利点もあり ます。つまり、 doctest を上手に書けば、ドキュメント作成とテストを同時に 片付けられて一石二鳥、というわけです。

    また、テストにまだ慣れていない開発者は、 doctest を使った方が早くテスト について学べることでしょう。

  • Java での開発経験のある開発者なら、 unittest フレームワークはとても 理解しやすいはずです。 unittest は Java の JUnit に影響を受けている ので、他の言語で JUnit から派生したテストフレームワークを使ったことがあ るのなら、unittest はかなりしっくりくるはずです。

  • 同じコードを含むような一連のテストを書くときには、 unittest フレー ムワークのクラスとメソッドによる設計が気に入るでしょう。共通のタスクを 抽象化して、メソッド化できるからです。また、 unittest フレームワー クは unittest はテストの初期化/終了処理のルーチンをサポートしてい るので、テストケースを実行するときの環境を高い水準でコントロールできま す。

繰り返しになりますが、(一つのアプリケーションの中であっても) 両方のシステム を並べて使えることを忘れないでください。どちらのテストシステムにも、状況に 応じて適した部分があるので、大抵のプロジェクトでは、最終的には両方のテスト システムを使うことになるものです。

テストを実行する

テストを実行するには、プロジェクトの manage.py ユーティリティを使います:

$ ./manage.py test

特定のアプリケーションに対してテストを実行したければ、コマンドラインにアプ リケーションの名前を追加します。例えば、 INSTALLED_APPSmyproject.pollsmyproject.animals というアプリケーションが入って おり、 animals の単体テストを実行したいだけなら、以下のようにします:

$ ./manage.py test animals

animals ではなく、 myproject.animals なので注意してください。

実行したいテストを選べるようになりました。

単体テストを実行する際、どのテストを実行するかを指定できます。あるアプリケー ション用のテストケースを実行する場合 (例えば、上で解説した AnimalTestCase の場合) は、コマンドラインにテストケースの名前を追加してください:

$ ./manage.py test animals.AnimalTestCase

テストケース中の 個別の テストメソッドを実行したければ、メソッド名を指定して ください:

$ ./manage.py test animals.AnimalTestCase.testFluffyAnimals

テストデータベース

データベースを必要とするテスト (モデルのテスト) に、「本番の (実運用環境の)」 データベースは必要ありません。テストの際には、空の別のデータベースが作成さ れます。

テストがパスするか失敗するかに関わらず、テストデータベースは全てのテストを 実行し終えると消去されます。

デフォルトでは、テストデータベースの名前は、 DATABASE_NAME に指 定したデータベース名の前に test_ を付けたものです。 SQLite データベース エンジンを使っている場合、デフォルトではテストをメモリ上のデータベースで行 います(すなわち、データベースをメモリ上に生成し、ファイルシステムを全く経由 しません!) 。テストデータベースの名前をデフォルト意外の値にしたければ、 TEST_DATABASE_NAME 設定を使って指定します。

テスト用に別のデータベースを使うことを除けば、テストランナは設定ファイルの データベースに関する他の設定、 DATABASE_ENGINE, DATABASE_USER, DATABASE_HOST などをそのまま使います。 テストデータベースは DATABASE_USER の権限で作成されるので、この ユーザは新たに生成されたデータベースを操作する権限を備えていなければなりま せん。

テスト用データベースの文字セットエンコーディング設定を細かく調整したいのな ら、 TEST_DATABASE_CHARSET 設定を使ってください。 MySQL を使って いるなら、 TEST_DATABASE_COLLATION でテストデータベースで使うコ レーション(collation) を指定できます。これらの設定については、 settings ファイルのドキュメント を参照してください。

その他のテスト条件

設定ファイル上の DEBUG の値にかかわらず、 Django は全てのテスト を 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 は、テストを書くときに便利なツールをいくつか提供しています。

テストクライアント

テストクライアント (test client) は、簡単なダミーブラウザとして動作する Python のクラスです。テストクライアントを使うと、ビューをテストしたり、 プログラムを使ってDjango で作られたアプリケーションとやりとりできます。

テストクライアントを使ってできることをいくつか挙げましょう:

  • ある URL に対する GET や POST をシミュレートでき、低水準の HTTP (レスポ ンスヘッダや状態コード) 情報から、ページの内容まで、リクエストに対するレ スポンスの全てを調べられます。
  • 特定の URL に対して正しいビューが呼び出されるかどうかを調べられます。
  • 特定のリクエストに対して、特定のテンプレートを使ったレンダリングが行わ れ、その際に特定の値が入ったコンテキストが使われているかどうかを調べら れます。

テストクライアントは TwillSelenium やその他のブラウザ自動化フレームワー クを置き換えようとするものではありません。 Django のテストクライアントはもっ と別の部分に焦点を当てているのです。すなわち:

  • 正しいビューが呼び出され、ビューが正しいコンテキストデータを生成してい るかどうかは、 Django のテストクライアントを使って調べてください。
  • Twill や Selenium は、 レンダリング済みの HTML や、 JavaScript の機能 のような Web ページの ビヘイビア のテストに使ってください。

網羅的なテストスイートでは、両方のタイプのテストを組み合わせて使うはずです。

テストの概要と簡単な例

テストクライアントを使うには、 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
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 ...'

上の例でわかるように、 Client は Python 対話インタプリタのセッション中 でもインスタンス化できます。

テストクライアントの動作には重要な点がいくつかあります:

  • テストクライアントを実行するために Web サーバを起動する必要は ありません 。実際、 Web サーバがまったく動いていなくても、テストは 何の問題もなく実行できるのです。というのも、テストクライアントは HTTP 通信のオーバヘッドを回避して、 Django フレームワークに直接アクセスし ているからです。このからくりによって、ユニットテストを高速に実行でき ます。

  • ページを取得するときには、ドメインを含まない パス部分だけ を指定す るよう気をつけてください。例えば、以下の呼び出し:

    >>> c.get('/login/')
    

    は正しいですが、次の呼び出し:

    >>> c.get('http://www.example.com/login/')
    

    は正しくありません。

    Django のプロジェクトから生成されていない Web ページは、テストクライ アントで取得できません。Django 以外の Web ページを取得したければ、 urlliburllib2 のような Python 標準ライブラリを使ってください。

  • テストクライアントは URL の解決に ROOT_URLCONF に指定され た URLconf を使います。

  • 上の例は Python の対話インタプリタ中でも動作しますが、一部のテンプレー ト関連の機能などは テストの実行中だけ でしか使えません。

    というのも、 Django のテストランナは、あるビューでどのテンプレートが ロードされるかを決定するためにちょっとした黒魔術的なコードを使ってい るからです。この黒魔術 (実際には、メモリ上のテンプレートシステムに対 するパッチ) は、テスト実行時にしか適用されません。

リクエスト生成

リクエストの生成には、 django.test.client.Client クラスを使います。 Client は引数なしで生成します。:

class Client

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 django.http.HttpRequest.is_ajax() method.

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)]
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) する場合 (例えば、 <select multiple> の選択結果を指定する場合など) は、キーに対す る値をリストやタプルにしてください。例えば、choices という名前 のフィールドから 3 つの行を選択して提出したければ:

{'choices': ('a', 'b', 'd')}

のようにします。

ファイルの送信には特別な扱いが必要です。ファイルの POST を行う場合、 以下のように、ファイル名フィールドの名前をキーに、アップロードした いファイルのファイルハンドルを値に設定します:

>>> c = Client()
>>> f = open('wishlist.doc')
>>> c.post('/customers/wishes/', {'name':'fred', 'attachment':f})
>>> f.close()

(attachment という名前には特に意味はありません。ファイルを処理 するコードで使いたい名前を指定してください)

送信するファイルのハンドルは、 post() 後に手動で閉じねばならな いので注意してください。

The extra argument acts the same as for Client.get().

If the URL you request with a POST contains encoded parameters, these parameters will be made available in the request.GET data. For example, if you were to make the request:

>>> c.post('/login/?vistor=true', {'name': 'fred', 'passwd': 'secret'})

... the view handling this request could interrogate request.POST to retrieve the username and password, and could interrogate request.GET to determine if the user was a visitor.

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.

head(path, data={}, follow=False, **extra)

Makes a HEAD request on the provided path and returns a Response object. Useful for testing RESTful interfaces. Acts just like Client.get() except it does not return a message body.

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.

options(path, data={}, follow=False, **extra)

Makes an OPTIONS request on the provided path and returns a Response object. Useful for testing RESTful interfaces.

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.

The extra argument acts the same as for Client.get().

put(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra)

Makes an PUT request on the provided path and returns a Response object. Useful for testing RESTful interfaces. Acts just like Client.post() except with the PUT request method.

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.

delete(path, follow=False, **extra)

Makes an DELETE request on the provided path and returns a Response object. Useful for testing RESTful interfaces.

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.

The extra argument acts the same as for Client.get().

login(**credentials)

Django の 認証システム を使っていて、ログイン 済みのユーザを扱う必要がある場合、テストクライアントの login() メソッドを使えば、サイトへログインするユーザをシミュレートできます。

このメソッドを呼び出すと、テストクライアントはログインに必要なクッ キーとセッションデータを持つようになり、テスト対象のビューのうち、 ログインの必要なテストをパスできるようになります。

credentials 引数の形式は、使っている 認証バックエンド によって変わりま す (認証バックエンドは AUTHENTICATION_BACKENDS で設定し ます)。 Django が提供している標準の認証バックエンド (ModelBackend) を使う場合、 credentials にはユーザのユーザ 名とパスワードをキーワー引数で指定します:

>>> c = Client()
>>> c.login(username='fred', password='secret')

# これでログイン保護されたビューに入れるようになりました。

認証バックエンドを変えた場合、このメソッドは違った認証情報を要求す るかもしれません。その場合、 login() は、認証バックエンドの authenticate() メソッドが必要とする認証情報をそのまま要求します。

login() は、指定した認証情報が承認され、ログインに成功した場合に True を返します。

このメソッドを使うには、事前にユーザを登録しておかねばなりません。上 で説明したように、テストランナはテストデータベースを使ってテストを実 行するので、デフォルトではユーザが登録されていません。そのため、運用 環境で使えるユーザはテスト環境では使えないのです。テストスイートの一 環として、手動で (Django のモデル API を使って) ユーザを作成するか、 フィクスチャでユーザを登録してください。 Remember that if you want your test user to have a password, you can’t set the user’s password by setting the password attribute directly – you must use the set_password() function to store a correctly hashed password. Alternatively, you can use the create_user() helper method to create a new user with a correctly hashed password.

logout()

Django の 認証システム を使っている場合、 logout() メソッドで、サイトからユーザをログアウトさせる効果をシ ミュレートできます。

このメソッドを呼び出すと、テストクライアントはクッキーとセッション データを全て消去して、デフォルトの状態に戻します。それ以降のリクエ ストは、ビューからは AnonymousUser から送信されたリクエストとみ なされます。

レスポンスのテスト

get() および post() メソッドは、いずれも Response オブジェクト を返します。 Response オブジェクトは Django のビューが返す HttpResponse オブジェクトと同じ ではありません 。このオブジェクトは 検証用のテストコード作成に便利なデータをいくつか備えています。

Response オブジェクトには以下のようなプロパティがあります:

class Response
client

レスポンス要求の生成に使われたテストクライアントです。

content

レスポンスの本体部分 (body) です。ビューがレンダリングによって生成 した最終的なページコンテンツか、エラーメッセージです。

context

レスポンスのページコンテンツのレンダに使われた Context オブジェ クトです。

複数のテンプレートを使ってレンダリングを行った場合、 contextContext オブジェクトをレンダリングで使った順に並べたリストです。

Regardless of the number of templates used during rendering, you can retrieve context values using the [] operator. For example, the context variable name could be retrieved using:

>>> response = client.get('/foo/')
>>> response.context['name']
'Arthur'
request

レスポンスを得るために使用したリクエストデータです。

status_code

レスポンスの HTTP 状態コードです。全ての HTTP 状態コードのリストは RFC2616 を参照してください。

template

最終的なページコンテンツのレンダリングに使われた Template のイ ンスタンスです。テンプレートをファイルからロードした場合、テンプレー トのファイル名を template.name で調べられます ('admin/index.html' のような形式の文字列です)。

複数のテンプレートをレンダリングしている場合 (例えば テンプレートの継承 を使っている場合)、 template はレンダリング順に並んだ Template オブジェクトの リストです。

また、レスポンスオブジェクトを辞書のように扱っても、HTTPヘッダに設定された 値にアクセスできます。例えば、レスポンスのコンテンツタイプは response['Content-Type'] でアクセスできます。

例外

テストクライアントのアクセス先のビューが例外を送出するような場合、テストケー ス内で例外にアクセスできます。例外のテストを行うには、通常の try...catch ブロックか、 unittest.TestCase.assertRaises() を使いま す。

ただし、 Http404PermissionDenied, SystemExit といった例外は テストケースからアクセスできません。 Django はこれらの例外を内部的に捕捉し て、対応する適切な HTTP 応答コードに変換してしまうからです。これらの例外に 対しては、 response.status_code のチェックで対応してください。

セッションの永続性

テストクライアントの動作はステートフルです。あるレスポンスにクッキーが入っ ていると、クッキーはテストクライアント中に保存され、それ以降の get()post() リクエストで使われます。

テストクライアントは、クッキーの有効期限ポリシを守りません。クッキーを期限 切れにしたければ、該当クッキーを client.cookies から手動で削除するか、 新たな Client インスタンスを生成してください (全てのクッキーを除去しま す)。

テストクライアントには、永続セッション情報 (persistent state information) を保存するためのプロパティが二つあります。これらのプロパティは、必要に応じ てテスト条件の一部として検査できます。

Client.cookies

Python の SimpleCookie 型のオブジェクトで、全てのクライアントクッキー の現在値が入っています。詳しくは Cookie モジュールのドキュメント を 参照してください。

Client.session

セッション情報の入った辞書ライクなオブジェクトです。詳しくは セッションのドキュメント を参照してくださ い。

ユニットテストの例

テストクライアントを使った簡単なユニットテストを以下に示します:

import unittest
from django.test.client import Client

class SimpleTest(unittest.TestCase):
    def setUp(self):
        # unittest を使う場合、毎回 Client を生成する必要があります。
        self.client = Client()

    def test_details(self):
        # GET リクエストを発行します。
        response = self.client.get('/customer/details/')

        # レスポンスが 200 OK であるか調べます。
        self.failUnlessEqual(response.status_code, 200)

        # レンダリングされるコンテキストの customers の長さが 5 である
        # か確かます。
        self.failUnlessEqual(len(response.context['customers']), 5)

TestCaseクラス

Normal Python unit test classes extend a base class of unittest.TestCase. Django provides an extension of this base class:

class TestCase

このクラスは、Web サイトのテスト時に便利な機能をいくつか追加しています。

通常のユニットテストを unittest.TestCase から Django 独自の TestCase に移行するのは簡単で、単にベースクラスを unittest.TestCase から django.test.TestCase に変更するだけです。 django.test.TestCase は、標準の Python ユニットテスト機能はそのままに、さらに便利な機能を提供し ています。

class TransactionTestCase

Django TestCase classes make use of database transaction facilities, if available, to speed up the process of resetting the database to a known state at the beginning of each test. A consequence of this, however, is that the effects of transaction commit and rollback cannot be tested by a Django TestCase class. If your test requires testing of such transactional behavior, you should use a Django TransactionTestCase.

TransactionTestCase and TestCase are identical except for the manner in which the database is reset to a known state and the ability for test code to test the effects of commit and rollback. A TransactionTestCase resets the database before the test runs by truncating all tables and reloading initial data. A TransactionTestCase may call commit and rollback and observe the effects of these calls on the database.

A TestCase, on the other hand, does not truncate tables and reload initial data at the beginning of a test. Instead, it encloses the test code in a database transaction that is rolled back at the end of the test. It also prevents the code under test from issuing any commit or rollback operations on the database, to ensure that the rollback at the end of the test restores the database to its initial state. In order to guarantee that all TestCase code starts with a clean database, the Django test runner runs all TestCase tests first, before any other tests (e.g. doctests) that may alter the database without restoring it to its original state.

When running on a database that does not support rollback (e.g. MySQL with the MyISAM storage engine), TestCase falls back to initializing the database by truncating tables and reloading initial data.

Note

The TestCase use of rollback to un-do the effects of the test code may reveal previously-undetected errors in test code. For example, test code that assumes primary keys values will be assigned starting at one may find that assumption no longer holds true when rollbacks instead of table truncation are being used to reset the database. Similarly, the reordering of tests so that all TestCase classes run first may reveal unexpected dependencies on test case ordering. In such cases a quick fix is to switch the TestCase to a TransactionTestCase. A better long-term fix, that allows the test to take advantage of the speed benefit of TestCase, is to fix the underlying test problem.

デフォルトのテストクライアント

TestCase.client

django.test.TestCase クラスのインスタンス内に入っているテストケースは全 て、デフォルトの テストクライアント にアクセスできます。このテストクライ アントは self.client で参照できます。テストクライアントはテストごとに再 生成されるので、テスト間でクッキーのような状態情報が継承される心配はありま せん。

例えば、以下のコード:

import unittest
from django.test.client import Client

class SimpleTest(unittest.TestCase):
    def test_details(self):
        client = Client()
        response = client.get('/customer/details/')
        self.failUnlessEqual(response.status_code, 200)

    def test_index(self):
        client = Client()
        response = client.get('/customer/index/')
        self.failUnlessEqual(response.status_code, 200)

では、各テストで Client をインスタンス化していますが、実際は次のコード のように、 self.client を参照するだけでよいのです:

from django.test import TestCase

class SimpleTest(TestCase):
    def test_details(self):
        response = self.client.get('/customer/details/')
        self.failUnlessEqual(response.status_code, 200)

    def test_index(self):
        response = self.client.get('/customer/index/')
        self.failUnlessEqual(response.status_code, 200)

フィクスチャの読み込み

TestCase.fixtures

データベースがバックにあるウェブサイトのテストで、データベース上に何もデー タが入っていなければあまり意味はありません。データベースにテストデータを入 れやすくするために、 Django の独自 TestCase クラスは フィクスチャ (fixture) の読み込み機能を提供しています。

フィクスチャとは、一群のデータを Django がデータベースに取り込める形式にし たものです。例えば、ユーザアカウントを持つようなサイトを作っている場合、テ スト中は仮のユーザアカウントの入ったフィクスチャを作り、データベースに入れ ておくと便利です。

最も素直なフィクスチャの作成方法は、 manage.py dumpdata コマンドを使う というものです。 dumpdata を使うにはデータベースに既に何らかのデータが 入っていなければなりません。詳しくは dumpdata のドキュメント を参照してください。

Note

manage.py syncdb を実行したことがあるなら、あなたの知らないうちにフィ クスチャは使われています。最初に syncdb を実行したときに、 Django は initial_data という名前のフィクスチャをインストールします。この メカニズムを使えば、 (カテゴリのデフォルトセットのような) 新たなデータ セットをデータベースに組み込めます。

initial_data 以外の名前のフィクスチャは、 django-admin.py loaddata で手動で組み込めます。

単体テストでフィクスチャを使う場合、フィクスチャを作成して Django のプロジェ クトディレクトリ下に置き、 dhango.test.TestCase のサブクラスの fixtures クラス属性を指定します:

from django.test import TestCase
from myapp.models import Animal

class AnimalTestCase(TestCase):
    fixtures = ['mammals.json', 'birds']

    def setUp(self):
        # 従来通りのテスト定義

    def testFluffyAnimals(self):
        # フィクスチャを使うテスト

ユニットテストは、以下のように動作します:

  • テストケースの開始時で、かつ setUp() の実行前に、 Django はデータベー スを一掃して、データベースを syncdb 直後の状態に戻します。
  • 次に、テストケースクラスに指定した全てのフィクスチャをインストールしま す。上の例では、 mammals という名前の JSON フィクスチャと、 birds という名前のフィクスチャがインストールされます。フィクスチャ の定義とインストールに関する詳細は loaddata のドキュメント を参照してください。

このフラッシュ/ロードの作業はテストケースの実行毎に繰り返されるので、テス トの結果が他のテストや以前に実行したテストの影響を受けたり、テストの順番に 左右されることはありません。

URLconf の設定

TestCase.urls

アプリケーションでビューを定義している場合、テストクライアントを使ってビュー を検査するテストを組み込みたいことがあるでしょう。しかし、エンドユーザはア プリケーションのビューを自分の好きな URL でデプロイできるので、テストを行う 際には、ビューが特定の URL でアクセスできると期待してはなりません。

テスト用の URL 空間を指定するために、 django.test.TestCase はテストスイー ト実行中に使われる URLconf をカスタマイズできる仕組みを提供しています。 TestCase インスタンスに urls 属性を指定しておくと、 TestCase はその値をテスト実行中だけ ROOT_URLCONF に使います。

使い方を示しましょう:

from django.test import TestCase

class TestMyViews(TestCase):
    urls = 'myapp.test_urls'

    def testIndexPageView(self):
        #  ``Client`` を使ってビューをテストする

このテストケースは、ケースの実行中だけ、 myapp.test_urls を URLconf と して使います。

メール送信箱の内容抹消

Django 独自の TestCase クラスを使うと、テストランナは各テストケースの開 始時にメールの送信箱に入っている内容を消去します。

テスト中のメールサービスの詳細は メールサービス を参照してください。

アサーション

通常の Python unittest.TestCase クラスが備えている assertTrueassertEquals のようなアサーションメソッドと同様、 Django 独自の TestCase クラスでも Web アプリケーションをテストする上で便利な様々な 独自のアサーションメソッドを提供しています。

TestCase.assertContains(response, text, count=None, status_code=200)

status_code に指定した状態コードで Response インスタンスが生成 され、レスポンスのコンテンツ内に text が入っているかどうか調べるア サーションです。 count を指定すると、 text は正確に count 回出現せねばなりません。

TestCase.assertNotContains(response, text, status_code=200)

status_code に指定した状態コードで Response インスタンスが生成 され、レスポンスのコンテンツ内に text が入っていないことを確認する アサーションです。

TestCase.assertFormError(response, form, field, errors)

form に指定したフォーム上のフィールド field で、 errors に 指定したエラーリストと同じエラーが送出されるかどうか調べるアサーション です。

form はテンプレートコンテキスト上の Form インスタンスの名前です。

field はフォーム内のフィールドの名前です。 field の値が None であれば、非フィールドエラー (特定のフィールドに関連付けられて いない、 form.non_field_errors() でアクセスできるエラー) がチェック されます。

errors はエラー文字列か、エラー文字列のリストです。フォームの検証結 果として返されるのと同じ形式です。

assertTemplateUsed(response, template_name)

template_name に指定したテンプレートがレスポンスのレンダリングに使 われていることを確認するアサーションです。

template_name'admin/index.html' のような文字列です。

assertTemplateNotUsed(response, template_name)

template_name に指定したテンプレートがレスポンスのレンダリングに使 われて いない ことを確認するアサーションです。

assertRedirects(response, expected_url, status_code=302, target_status_code=200)

レスポンスが status_code に指定したリダイレクト状態コードを持ち、か つ expected_url へのリダイレクトを指示しており、さらにリダイレクト 先の内容を HTTP 状態コード target_status_code で取得できるかどうか 調べるアサーションです。

If your request used the follow argument, the expected_url and target_status_code will be the url and status code for the final point of the redirect chain.

メールサービス

ビューが Django のメールサービス を使っている場合、 ビューをテストするたびにメールが送信されてほしくはないでしょう。 Django の テストランナは、テスト中に Django から送信されたすべてのメールをダミーの送 信箱に振り分けます。これによって、送信したメールの数から、各メールのメッセー ジに至るまで、メール送信機能のすべてを (実際にメールを送らずに) テストでき ます。

Django は、テストフレームワークを初期化する際、通常の SMTPConnection クラスをダミーの SMTPConnection 実装に切替えます (Django の外部のメール送信機構、例えば Django を動かしてい るホストで動いているメールサーバには一切影響を及ぼしません)。

django.core.mail.outbox

テストの実行中、 Django から送信されたメールは djnago.core.mail.outbox に保存されます。 djnago.core.mail.outbox はその時点で送信された全ての EmailMessage インスタンスからなるリストで、テ スト時以外には存在しません。このダミー送信箱は、テストのセットアップ時に、 ダミーの SMTPConnection と組で初期化されます。テ ストが終了すると、通常の SMTPConnection が復帰し、 ダミー送信箱は削除されます。

The outbox attribute is a special attribute that is created only when the tests are run. It doesn’t normally exist as part of the django.core.mail module and you can’t import it directly. The code below shows how to access this attribute correctly.

django.core.mail.outbox を調べて、メール送信数やメールの内容をチェック するコード例を以下に示します:

from django.core import mail
from django.test import TestCase

class EmailTest(TestCase):
    def test_send_email(self):
        # Send message.
        mail.send_mail('Subject here', 'Here is the message.',
            'from@example.com', ['to@example.com'],
            fail_silently=False)

        # Test that one message has been sent.
        self.assertEqual(len(mail.outbox), 1)

        # Verify that the subject of the first message is correct.
        self.assertEqual(mail.outbox[0].subject, 'Subject here')

前節で 述べたように、テスト用の送信箱の内容は TestCase 内の各テストケースの開始時点で抹消されます。手動で送信箱の内容を空 にしたければ、空のリストを mail.outbox に代入してください:

from django.core import mail

# Empty the test outbox
mail.outbox = []

他のテストフレームワークを使う

doctestunittest だけが Python のテストフレームワークではありま せん。Django は他のテストフレームワークを明にサポートしているわけではありま せんが、他のテストフレームワークで作成したテストを Django の通常のテストと 同じように呼び出すためのメカニズムは提供しています。

./manage.py test を実行すると、 Django は TEST_RUNNER 設定を 探して、実行すべきテストランナを決定します。デフォルトでは、 TEST_RUNNERdjango.test.simple.run_tests です。このメソッ ドは Django のデフォルトのテスト動作を実装しており、以下のように動作します:

  1. テスト前の全体的な準備を行います。
  2. テストデータベースを生成します。
  3. syncdb を実行して、モデルと初期データをデータベースにインストールし ます。
  4. インストールされている全てのアプリケーションの models.pytests.py から、単体テストと doctest を探します。
  5. 見つかった単体テストと doctest を実行します。
  6. テストデータベースを破壊します。
  7. テスト後の全体的な後始末を行います。

テストランナメソッドを自作して、そのメソッド名を TEST_RUNNER に 指定しておき、 ./manage.py test を実行すると、 Django はそのテストラン ナメソッドを実行します。これによって、 Python コードから任意のテストフレー ムワークを実行できます。

テストランナを定義する

慣習的に、テストランナは run_tests と名付けます。テストランナは以下のよ うに Django テストランナと同じ引数をとらねばなりません:

run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[])

test_labels には、実行したいテストを表す文字列 (ラベル) のリストを 指定します。ラベルは以下の 3 つの形式で指定できます:

  • app.TestCase.test_method - テストケース中の個別のテストメソッ ドを実行します。
  • app.TestCase - テストケース中の全てのテストメソッドを実行しま す。
  • app - アプリケーション中の全てのテストを探して実行します。

test_labelsNone の場合、テストランナは INSTALLED_APPS に登録されている全てのアプリケーションからテ ストを探して実行します。

verbosity には、コンソールに出力される通知情報やデバッグ情報の量を 指定します。 0 にすると何も出力しません。 1 は通常の出力、 2 は多めの出力です。

interactiveTrue にすると、テストスイートの実行時に、テスト スイートからユーザに質問できるようになります。例えば、テストデータベー スを削除してよいか尋ねるといった使い方です。 interactiveFalse にする場合、テストスイートは手動操作なしで実行できねばなりま せん。

extra_tests には、このテストランナに追加で実行させたい TestCase インスタンスを指定します。 extra_tests を指定すると、 module_list から発見したテストに加えて、指定したテストを実行します。

このメソッドは失敗したテストの数を返さねばなりません。

テスト用のユーティリティ

django.test.utils モジュールには、テストランナの自作に役立つユーティリ ティメソッドが定義されています。

setup_test_environment()

テンプレートレンダリングシステム構成のインストールなど、テスト前の全 体的な準備を行い、ダミーの SMTPConnecton をセットアップします。

teardown_test_environment()

テンプレートレンダリングシステムに組み込まれた黒魔術的なフックの除去な ど、テスト後の全体的な後始末を行い、メールサービスを通常の状態に復帰し ます。

データベースバックエンドの生成モジュール (connection.creation) にも、テ スト時に便利なユーティリティがあります。

create_test_db(verbosity=1, autoclobber=False)

新たなテストデータベースを生成し、 syncdb を実行します。

verbosityrun_tests() の同名の引数と同じ意味です。

autoclobber には、テストデータベースと同名のデータベースが見つかっ た際の挙動を指定します:

  • autoclobberFalse なら、既存のデータベースを破壊しても よいかユーザに尋ねます。ユーザが「いいえ」を選択すると、その時点 で sys.exit を呼び出して終了します。
  • autoclobberTrue なら、既存のデータベースを破壊すると きにユーザに了解を求めません。

生成されたテストデータベースの名前を返します。

create_test_db() には settings.DATABASE_NAME を変更してテストデー タベースの名前にするという副作用があります。

create_test_db() 生成されたテストデータベースの名前を返すように なりました。
destroy_test_db(old_database_name, verbosity=1)

このメソッドを呼び出した時点で DATABASE_NAME に設定されてい るデータベースを破壊し、 DATABASE_NAME の値を old_database_name に指定したデータベース名に戻します。

verbosityrun_tests() の同名の引数と同じ意味です。