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 は、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.py
と tests.py
に入っている全てのテストケー
ス (つまり unittest.TestCase
のサブクラス) を捜し出し、そこからテストス
イートを自動的に構築して、スイートを実行します。
開発バージョンの Django には、あるモジュールのテストスイートを定義する方法
をもう一つ提供しています: models.py
や tests.py
で suite()
メ
ソッドを定義している場合、 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_APPS
に
myproject.polls
と myproject.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 に対して正しいビューが呼び出されるかどうかを調べられます。
- 特定のリクエストに対して、特定のテンプレートを使ったレンダリングが行わ れ、その際に特定の値が入ったコンテキストが使われているかどうかを調べら れます。
テストクライアントは Twill や Selenium やその他のブラウザ自動化フレームワー クを置き換えようとするものではありません。 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 ページを取得したければ、 urllib や urllib2 のような Python 標準ライブラリを使ってください。
テストクライアントは URL の解決に
ROOT_URLCONF
に指定され た URLconf を使います。上の例は Python の対話インタプリタ中でも動作しますが、一部のテンプレー ト関連の機能などは テストの実行中だけ でしか使えません。
というのも、 Django のテストランナは、あるビューでどのテンプレートが ロードされるかを決定するためにちょっとした黒魔術的なコードを使ってい るからです。この黒魔術 (実際には、メモリ上のテンプレートシステムに対 するパッチ) は、テスト実行時にしか適用されません。
リクエストの生成には、 django.test.client.Client
クラスを使います。
Client
は引数なしで生成します。:
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
オブジェクトには以下のようなプロパティがあります:
Response
¶client
¶レスポンス要求の生成に使われたテストクライアントです。
content
¶レスポンスの本体部分 (body) です。ビューがレンダリングによって生成 した最終的なページコンテンツか、エラーメッセージです。
context
¶レスポンスのページコンテンツのレンダに使われた Context
オブジェ
クトです。
複数のテンプレートを使ってレンダリングを行った場合、 context
は
Context
オブジェクトをレンダリングで使った順に並べたリストです。
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
¶レスポンスを得るために使用したリクエストデータです。
また、レスポンスオブジェクトを辞書のように扱っても、HTTPヘッダに設定された
値にアクセスできます。例えば、レスポンスのコンテンツタイプは
response['Content-Type']
でアクセスできます。
テストクライアントのアクセス先のビューが例外を送出するような場合、テストケー
ス内で例外にアクセスできます。例外のテストを行うには、通常の
try...catch
ブロックか、 unittest.TestCase.assertRaises()
を使いま
す。
ただし、 Http404
や PermissionDenied
, SystemExit
といった例外は
テストケースからアクセスできません。 Django はこれらの例外を内部的に捕捉し
て、対応する適切な HTTP 応答コードに変換してしまうからです。これらの例外に
対しては、 response.status_code
のチェックで対応してください。
テストクライアントの動作はステートフルです。あるレスポンスにクッキーが入っ
ていると、クッキーはテストクライアント中に保存され、それ以降の get()
や
post()
リクエストで使われます。
テストクライアントは、クッキーの有効期限ポリシを守りません。クッキーを期限
切れにしたければ、該当クッキーを client.cookies
から手動で削除するか、
新たな Client
インスタンスを生成してください (全てのクッキーを除去しま
す)。
テストクライアントには、永続セッション情報 (persistent state information) を保存するためのプロパティが二つあります。これらのプロパティは、必要に応じ てテスト条件の一部として検査できます。
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)
Normal Python unit test classes extend a base class of unittest.TestCase
.
Django provides an extension of this base class:
TestCase
¶このクラスは、Web サイトのテスト時に便利な機能をいくつか追加しています。
通常のユニットテストを unittest.TestCase
から Django 独自の
TestCase
に移行するのは簡単で、単にベースクラスを unittest.TestCase
から django.test.TestCase
に変更するだけです。 django.test.TestCase
は、標準の Python ユニットテスト機能はそのままに、さらに便利な機能を提供し
ています。
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 のドキュメント
を参照してください。
このフラッシュ/ロードの作業はテストケースの実行毎に繰り返されるので、テス トの結果が他のテストや以前に実行したテストの影響を受けたり、テストの順番に 左右されることはありません。
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
クラスが備えている assertTrue
や
assertEquals
のようなアサーションメソッドと同様、 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.',
'[email protected]', ['[email protected]'],
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 = []
doctest
と unittest
だけが Python のテストフレームワークではありま
せん。Django は他のテストフレームワークを明にサポートしているわけではありま
せんが、他のテストフレームワークで作成したテストを Django の通常のテストと
同じように呼び出すためのメカニズムは提供しています。
./manage.py test
を実行すると、 Django は TEST_RUNNER
設定を
探して、実行すべきテストランナを決定します。デフォルトでは、
TEST_RUNNER
は django.test.simple.run_tests
です。このメソッ
ドは Django のデフォルトのテスト動作を実装しており、以下のように動作します:
- テスト前の全体的な準備を行います。
- テストデータベースを生成します。
syncdb
を実行して、モデルと初期データをデータベースにインストールし ます。- インストールされている全てのアプリケーションの
models.py
やtests.py
から、単体テストと doctest を探します。- 見つかった単体テストと doctest を実行します。
- テストデータベースを破壊します。
- テスト後の全体的な後始末を行います。
テストランナメソッドを自作して、そのメソッド名を 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_labels
が None
の場合、テストランナは
INSTALLED_APPS
に登録されている全てのアプリケーションからテ
ストを探して実行します。
verbosity
には、コンソールに出力される通知情報やデバッグ情報の量を
指定します。 0
にすると何も出力しません。 1
は通常の出力、
2
は多めの出力です。
interactive
を True
にすると、テストスイートの実行時に、テスト
スイートからユーザに質問できるようになります。例えば、テストデータベー
スを削除してよいか尋ねるといった使い方です。 interactive
を
False
にする場合、テストスイートは手動操作なしで実行できねばなりま
せん。
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
を実行します。
verbosity
は run_tests()
の同名の引数と同じ意味です。
autoclobber
には、テストデータベースと同名のデータベースが見つかっ
た際の挙動を指定します:
autoclobber
がFalse
なら、既存のデータベースを破壊しても よいかユーザに尋ねます。ユーザが「いいえ」を選択すると、その時点 でsys.exit
を呼び出して終了します。autoclobber
がTrue
なら、既存のデータベースを破壊すると きにユーザに了解を求めません。
生成されたテストデータベースの名前を返します。
create_test_db()
には settings.DATABASE_NAME
を変更してテストデー
タベースの名前にするという副作用があります。
create_test_db()
生成されたテストデータベースの名前を返すように
なりました。destroy_test_db
(old_database_name, verbosity=1)¶このメソッドを呼び出した時点で DATABASE_NAME
に設定されてい
るデータベースを破壊し、 DATABASE_NAME
の値を
old_database_name
に指定したデータベース名に戻します。
verbosity
は run_tests()
の同名の引数と同じ意味です。
Oct 26, 2017