テストツール¶
Django は、テストを書くのに便利なツールをいくつか提供しています。
テストクライアント¶
テストクライアントは、ダミーのウェブブラウザとして振る舞う Python のクラスです。これを使えば、ビューと Django で作ったアプリケーションとの関係をプログラムから自動でテストできるようになります。
テストクライアントでは、次のようなことができます。
ある URL に対する GET および POST リクエストのシミュレートと、その結果の観察。低レベルの HTTP (レスポンスのヘッダーやステータスコードなど) から、ページのコンテンツまで、あらゆるレスポンスの内容を調査できます。
一連の (好きな数の) リダイレクトを見て、その各ステップごとに URL とステータスコードをチェックする。
指定したリクエストが特定の Django テンプレートによってレンダリングされたとき、テンプレートのコンテキストが正しく特定の値を含んでいるかどうかをテストする。
テストクライアントは Selenium や他の "ブラウザ内 (in-browser)" フレームワークの代替を目指すものではないことに注意してください。Django のテストクライアントの目的は別の点にあります。つまり、
Django のテストクライアントは、正しいテンプレートがレンダリングされ、そのテンプレートが正しいコンテキストデータをちゃんと渡しているのかをチェックするために使います。
ビューの機能を直接テストするには、
RequestFactory
を使用して、ルーティングとミドルウェアのレイヤーをバイパスしてください。Selenium などのブラウザー内フレームワークは、HTML の レンダリング や、ウェブページの 振る舞い 、つまり JavaScript の機能性をテストするために使います。また、Django はこれらのフレームワーク向けの特別なサポートも提供しています。詳細については、
LiveServerTestCase
のセクションを読んでください。
包括的なテストスイートを実現するには、これらすべてのタイプのテストを組み合わせて行うべきです。
概要と簡単な例¶
テストクライアントを使うには、django.test.Client
からインスタンスを作り、次のようにウェブページを取得します。
>>> from django.test import Client
>>> c = Client()
>>> response = c.post("/login/", {"username": "john", "password": "smith"})
>>> response.status_code
200
>>> response = c.get("/customer/details/")
>>> response.content
b'<!DOCTYPE html...'
この例が示唆しているように、 Client
のインスタンスは、Python のインタラクティブなインタプリタ上のセッションからでも作ることができます。
テストクライアントの動作の仕方に関して、いくつか大切な注意点があります。
テストクライアントが動作するために、ウェブサーバーが動作している必要は ありません 。実際、ウェブサーバーを一切起動せずに動作するのです! HTTP のオーバーヘッドを避けて、Django フレームワークと直接やりとりすることでこれを実現しています。これにより、高速なユニットテストが可能になっています。
ページの取得時には、ドメイン全体ではなく、URL の path だけを指定することを覚えておいてください。たとえば、次は正しいです。
>>> c.get("/login/")
しかし、これは間違いです。
>>> c.get("https://www.example.com/login/")
テストクライアントには、自分の Django プロジェクト以外のウェブページを取得する機能はありません。他のウェブページが必要な場合には、
urllib
などの Python のスタンダードライブラリモジュールを利用してください。URL を解決するとき、テストクライアントは
ROOT_URLCONF
設定で指定されたすべての URLconf を使用します。上の例では Python のインタラクティブなインタプリタ上でも動作するはずですが、テストクライアントの一部の機能、特にテンプレート関係の機能は、 テストの実行中 にしか使えないことがあります。
というのも、Django のテストランナーは、与えられたビューによって読み込まれるテンプレートを決定する時に、ちょっとした黒魔術を使っています。この黒魔術 (具体的には Django のテンプレートシステムに対してメモリ上でパッチを当てています) は、テストの実行中にだけ使われるのです。
デフォルトでは、テストクライアントはサイト上でのすべての CSRF チェックを無効にしています。
何らかの理由でテストクライアントに CSRF チェックを実行して ほしい ときには、CSRF チェックの実行を強制するテストクライアントのインスタンスを作ることができます。これには、クライアントを作る時に次のように
enforce_csrf_checks
引数を渡します。>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
リクエストの作成¶
リクエストの作成には、django.test.Client
クラスを使います。
- class Client(enforce_csrf_checks=False, raise_request_exception=True, json_encoder=DjangoJSONEncoder, *, headers=None, query_params=None, **defaults)[ソース]¶
テスト用の HTTP クライアントです。いくつかの引数で動作をカスタマイズできます。
headers
にはリクエストごとに送信されるデフォルトのヘッダを指定できます。例えば、User-Agent
ヘッダの場合:client = Client(headers={"user-agent": "curl/7.79.1"})
query_params
allows you to specify the default query string that will be set on every request.Defaults
内の任意のキーワード引数には WSGI 環境変数 を指定します。例えば、スクリプト名の場合:client = Client(SCRIPT_NAME="/app/")
注釈
HTTP_
プレフィックスで始まるキーワード引数がヘッダーとして設定されますが、可読性のためにheaders
パラメーターの使用を推奨します。The values from the
headers
,query_params
, andextra
keyword arguments passed toget()
,post()
, etc. have precedence over the defaults passed to the class constructor.enforce_csrf_checks
引数を使うと、CSRF プロテクションのテストが実行できます (上の説明を参照)。raise_request_exception
引数を指定することで、リクエスト中に発生した例外をテストでも発生させるかどうかを制御できます。デフォルトはTrue
です。json_encoder
引数を指定すると、post()
に記述されているJSONシリアライズ用のカスタムJSONエンコーダを指定できます。Changed in Django 5.1:The
query_params
argument was added.Client
インスタンスを一度作れば、以下のメソッドを自由に使うことができます。- get(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[ソース]¶
与えられた
path
に対して GET リクエストを作り、Response
オブジェクトを返します。Response
オブジェクトについては、下のセクションにドキュメントされています。The key-value pairs in the
query_params
dictionary are used to set query strings. For example:>>> c = Client() >>> c.get("/customers/details/", query_params={"name": "fred", "age": 7})
引数の評価の結果、次の GET リクエストの実行と等価になります。
/customers/details/?name=fred&age=7
It is also possible to pass these parameters into the
data
parameter. However,query_params
is preferred as it works for any HTTP method.headers
引数は、リクエスト時に送信されるヘッダーの指定に使われます。たとえば、次のコード>>> c = Client() >>> c.get( ... "/customers/details/", ... query_params={"name": "fred", "age": 7}, ... headers={"accept": "application/json"}, ... )
は、HTTP ヘッダー
HTTP_ACCEPT
を details ビューに送信します。これは、django.http.HttpRequest.accepts()
メソッドを使ったコードパースのテストのための良い方法です。任意のキーワード引数には、WSGI 環境変数 を指定します。たとえば、スクリプト名を設定するヘッダの場合:
>>> c = Client() >>> c.get("/", SCRIPT_NAME="/app/")
GET の引数がすでに URL エンコードされた形式である場合は、data 引数の代わりにエンコード済みの文字列を使うことができます。たとえば、先ほどの GET リクエストは次のようにも書けます。
>>> c = Client() >>> c.get("/customers/details/?name=fred&age=7")
If you provide a URL with both an encoded GET data and either a query_params or data argument these arguments will take precedence.
follow
をTrue
を与えると、クライアントはすべてのリダイレクトを辿り、途中の URL とステータスコードのタプルが、レスポンスオブジェクトのredirect_chain
属性に追加されてゆきます。たとえば、URL
/redirect_me/
が/next/
にリダイレクトし、それがさらに/final/
にリダイレクトするような場合には、redirect_chain
は次のような値になります。>>> response = c.get("/redirect_me/", follow=True) >>> response.redirect_chain [('http://testserver/next/', 302), ('http://testserver/final/', 302)]
secure
をTrue
に設定すると、クライアントは HTTPS リクエストをエミュレートします。Changed in Django 5.1:The
query_params
argument was added.
- post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, query_params=None, **extra)[ソース]¶
与えられた
path
に対して POST リクエストを作り、Response
オブジェクトを返します。Response
オブジェクトについては、下のセクションにドキュメントされています。data
ディクショナリ内の key-value ペアは、POST データを送信するのに使われます。例えば、次の例では、>>> c = Client() >>> c.post("/login/", {"name": "fred", "passwd": "secret"})
引数の評価の結果、次の URL へ POST リクエストが行われます。
/login/
リクエストで送られる POST データは次のものになります。
name=fred&passwd=secret
content_type
に application/json を指定すると、data
が dict、リスト、タプルの場合、json.dumps()
を使ってシリアライズされます。デフォルトではDjangoJSONEncoder
でシリアライズを行いますが、Client
にjson_encoder
引数を指定することでオーバーライドできます。このシリアライズはput()
,patch()
,delete()
リクエストでも行われます。もし他の
content_type
(たとえば、XML データの場合は text/xml) を与えると、data
のコンテンツは、HTTP のContent-Type
ヘッダーの中でcontent_type
を使ったものとして、POST リクエストで送られます。content_type
に値を渡さなかったときは、data
内の値を multipart/form-data のコンテンツタイプとして送信します。この場合は、data
内のkey-value ペアが multipart メッセージにえんこーどされ、POST データを生成するのに使われます。たとえば
<select multiple>
の複数の選択を指定する場合のように、特定のキーに対して複数の値を送信したいときは、必要なキーに対する値をリストまたはタプルとして与えます。たとえば、data
に次の値を与えれば、3つの選択した値をchoice
という名前のフィールドに対して送信できます。{"choices": ["a", "b", "d"]}
ファイルの送信は特別なケースです。ファイルをPOSTするには、キーとしてファイルフィールド名を、値としてアップロードしたいファイルへのファイルハンドルを渡すだけで十分です。例えば、フォームに
name
とattachment
のフィールドがあり、後者がFileField
の場合:>>> c = Client() >>> with open("wishlist.doc", "rb") as fp: ... c.post("/customers/wishes/", {"name": "fred", "attachment": fp}) ...
また、ファイルのようなオブジェクト (例えば
StringIO
やBytesIO
) をファイルハンドルとして指定することもできます。もしImageField
にアップロードする場合、オブジェクトにはvalidate_image_file_extension
バリデータを渡すname
属性が必要です。たとえば下記のようにします:>>> from io import BytesIO >>> img = BytesIO( ... b"GIF89a\x01\x00\x01\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00" ... b"\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01\x00\x00" ... ) >>> img.name = "myimage.gif"
複数の
post()
の呼び出しに対して同じファイルハンドラを使う時には、post 間でファイルポインタを手動でリセットする必要があります。これを一番簡単に扱う方法は、上に示したように、ファイルがpost()
に与えられた後に手動でファイルを close することです。データが読み込めるように、正しい方法でファイルを開くようにする必要があります。これはつまり、画像ファイルなどのバイナリデータが含まれている場合には、
rb
(read binary、バイナリ読み込み) モードで開かなければならないということです。The
headers
,query_params
, andextra
parameters acts the same as forClient.get()
.POST でリクエストした URL にエンコード済みパラメータが含まれている場合には、これらのデータは request.GET データから利用できます。たとえば、次のようなリクエストを行った場合、
>>> c.post( ... "/login/", {"name": "fred", "passwd": "secret"}, query_params={"visitor": "true"} ... )
このリクエストをハンドリングするビューでは、request.POST からはユーザー名とパスワードを取得し、request.GET からはユーザーが visitor であるかどうかを特定できます。
follow
をTrue
を与えると、クライアントはすべてのリダイレクトを辿り、途中の URL とステータスコードのタプルが、レスポンスオブジェクトのredirect_chain
属性に追加されてゆきます。secure
をTrue
に設定すると、クライアントは HTTPS リクエストをエミュレートします。Changed in Django 5.1:The
query_params
argument was added.
- head(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[ソース]¶
Makes a HEAD request on the provided
path
and returns aResponse
object. This method works just likeClient.get()
, including thefollow
,secure
,headers
,query_params
, andextra
parameters, except it does not return a message body.Changed in Django 5.1:The
query_params
argument was added.
- options(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[ソース]¶
与えられた
path
に対して OPTION リクエストを作り、Response
オブジェクトを返します。RESTful インターフェイスのテスト時に有用です。data
が与えられると、request body として使われます。content_type
はContent-Type
ヘッダーに設定されます。The
follow
,secure
,headers
,query_params
, andextra
parameters act the same as forClient.get()
.Changed in Django 5.1:The
query_params
argument was added.
- put(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[ソース]¶
与えられた
path
に対して PUT リクエストを作り、Response
オブジェクトを返します。RESTful インターフェイスのテスト時に有用です。data
が与えられると、request body として使われます。content_type
はContent-Type
ヘッダーに設定されます。The
follow
,secure
,headers
,query_params
, andextra
parameters act the same as forClient.get()
.Changed in Django 5.1:The
query_params
argument was added.
- patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[ソース]¶
与えられた
path
に対して PATCH リクエストを作り、Response
オブジェクトを返します。RESTful インターフェイスのテスト時に有用です。The
follow
,secure
,headers
,query_params
, andextra
parameters act the same as forClient.get()
.Changed in Django 5.1:The
query_params
argument was added.
- delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[ソース]¶
与えられた
path
に対して DELETE リクエストを作り、Response
オブジェクトを返します。RESTful インターフェイスのテスト時に有用です。data
が与えられると、request body として使われます。content_type
はContent-Type
ヘッダーに設定されます。The
follow
,secure
,headers
,query_params
, andextra
parameters act the same as forClient.get()
.Changed in Django 5.1:The
query_params
argument was added.
- trace(path, follow=False, secure=False, *, headers=None, query_params=None, **extra)[ソース]¶
与えられた
path
に対して TRACE リクエストを作り、Response
オブジェクトを返します。診断のための調査をシミュレートするときに役に立ちます。他のリクエストメソッドとは違い、
data
がキーワード引数にありません。 RFC 9110 Section 9.3.8 に従うためです。そのため、TRACE リクエストには body を含むことが禁止されています。The
follow
,secure
,headers
,query_params
, andextra
parameters act the same as forClient.get()
.Changed in Django 5.1:The
query_params
argument was added.
- login(**credentials)¶
- alogin(**credentials)¶
非同期バージョン:
alogin()
あなたのサイトが Django の 認証システム を使っていて、ユーザーのログインをテストしたければ、テストクライアントの
login()
メソッドを使うことで、ユーザーがサイトにログインしたときの状況をシミュレートできます。このメソッドを呼ぶ事で、テストクライアントはログインに基づいてビューを形成するテストを行う上で必要なクッキーとセッション情報を全て持ちます。
引数
credentials
の形式は利用している (AUTHENTICATION_BACKENDS
の設定値に定義されています) 認証バックエンド に依存します。Django によって提供される標準の認証バックエンド (ModelBackend
) を用いている場合は、credentials
は利用者のユーザー名とパスワードであり、キーワード引数として渡されます:>>> c = Client() >>> c.login(username="fred", password="secret") # Now you can access a view that's only available to logged-in users.
もし標準以外の認証バックエンドを利用している場合、このメソッドは異なった認証情報を必要とします。この値では利用している認証バックエンドの
authenticate()
メソッドによって要求される認証情報が必要になります。認証情報が受け入れられてログインが成功した場合に
login()
はTrue
を返します。最後に、このメソッドを使う前にユーザーアカウントを作成する必要があります。上で説明したように、テストランナーはデフォルトではユーザーを含まないテスト用データベースを使用して実行されます。その結果、本番サイトで有効なユーザーアカウントはテスト条件下では動作しません。テストスイートの一部として、手動で(Django モデル API を使って)、あるいはテストフィクスチャを使ってユーザを作成する必要があります。テストユーザーにパスワードを持たせたい場合、 password 属性を直接設定してユーザーのパスワードを設定することはできないことを覚えておいてください。
set_password()
関数を使って、正しくハッシュ化されたパスワードを保存する必要があります。また、create_user()
ヘルパーメソッドを使って、正しくハッシュ化されたパスワードを持つ新しいユーザーを作成することもできます。Changed in Django 5.0:alogin()
メソッドが追加されました。
- force_login(user, backend=None)¶
- aforce_login(user, backend=None)¶
非同期バージョン:
aforce_login()
あなたのサイトが Django の 認証システム を使っている場合、
force_login()
メソッドを使うと、ユーザがサイトにログインしたときの影響をシミュレーションできます。テストがユーザのログインを必要とするが、ユーザがどのようにログインしたかの詳細は重要ではない場合にはlogin()
の代わりにこのメソッドを使用してください。login()
とは異なり、このメソッドは認証と確認のステップを省略します。アクティブでないユーザ (is_active=False
) もログインを許可され、ユーザの認証情報を提供する必要はありません。ユーザーには、
backend
引数の値(ドットで区切られたPythonパスの文字列であるべき)がbackend
属性としてセットされます。値が指定されていない場合は、settings.AUTHENTICATION_BACKENDS[0]
に設定されます。login()
によって呼び出されるauthenticate()
関数は、通常このようにユーザーにアノテーションを付けます。このメソッドは、高コストなパスワードハッシングアルゴリズムを回避するため、
login()
よりも高速です。また、 テストに弱いハッシャーを使う ことで、login()
処理を高速にすることもできます。Changed in Django 5.0:aforce_login()
メソッドが追加されました。
- logout()¶
- alogout()¶
非同期バージョン:
alogout()
あなたのサイトが Django の 認証システム を使っている場合、
logout()
メソッドを使うと、ユーザがサイトからログアウトしたときの効果をシミュレートできます。このメソッドを呼び出した後、テストクライアントはすべてのクッキーとセッションデータをデフォルトにクリアします。それ以降のリクエストは
AnonymousUser
から来たように見えるでしょう。Changed in Django 5.0:alogout()
メソッドが追加されました。
レスポンスのテスト¶
get()
および post()
メソッドは、両方とも Response
オブジェクトを返します。この Response
オブジェクトは、Django のビューによって返される HttpResponse
オブジェクトとは 異なるもの であり、テストのレスポンスには、テストコードの検証に便利な追加データがいくつかあります。
特に、Response
オブジェクトは以下の属性を持ちます:
- class Response¶
- client¶
レスポンスの結果の元となるリクエストを作るために使われた、テストクライアントです。
- content¶
Bytestring としてのレスポンスの本文です。ビューないしエラーメッセージによってレンダリングされる際の最終的なページコンテンツです。
- context¶
テンプレートの
Context
インスタンスです。レスポンスの content を生成するテンプレートをレンダリングする際に使われます。描画されたページが複数のテンプレートを使っていた場合、
context
はContext
オブジェクトのリストとなり、その順序はレンダリングされた順となります。レンダリングに使われるテンプレートの数にかかわらず、
[]
オペレータを使ってコンテキストの値を取り出すことができます。たとえば、コンテキストの変数name
は以下のように取り出せます。>>> response = client.get("/foo/") >>> response.context["name"] 'Arthur'
Django のテンプレートを使っていない?
この属性は、
DjangoTemplates
バックエンドを使用しているときのみ格納されます。他のテンプレートエンジンを使っている場合、context_data
がこの属性を扱うレスポンスの適切な選択肢となるでしょう。
- exc_info¶
ビュー中で発生した未処理の例外 (もし存在すれば) に関する情報を提供する3つの値のタプル。
値は (type, value, traceback) であり、Python の
sys.exc_info()
が返すのと同じものです。それぞれの値の意味は以下のとおりです。type: 例外の型。
value: 例外インスタンス。
traceback: 例外がもともと発生した場所におけるロールスタックをカプセル化した、トレースバックオブジェクト。
例外が発生しなかった場合、
exc_info
はNone
になります。
- json(**kwargs)¶
JSON としてパースされた、レスポンスの本文です。追加のキーワード引数が
json.loads()
に渡されます。たとえば、次のようになります。>>> response = client.get("/foo/") >>> response.json()["name"] 'Arthur'
Content-Type
ヘッダが"application/json"
ではない場合、レスポンスをパースする際にValueError
が投げられます。
- request¶
レスポンスを機能させるリクエストデータです。
- wsgi_request¶
レスポンスを生成したテストハンドラーによって生成された
WSGIRequest
インスタンス。
- status_code¶
レスポンスの HTTP ステータスで、数値です。 定義済みのコードの全リストは、IANA status code registry を参照してください。
- templates¶
最終コンテンツをレンダリングするときに使われる
Template
インスタンスのリストで、 レンダリングされる順となります。テンプレートがファイルから読み込まれる場合、テンプレートのファイル名を取得するためには、リスト内の各テンプレートに対してtemplate.name
を使ってください (名前は'admin/index.html'
のような文字列となります)。Django のテンプレートを使っていない?
この属性は、
DjangoTemplates
バックエンドを使用しているときのみ格納されます。他のテンプレートを知使っている場合、template_name
が、レンダリングに使われるテンプレートの名前のみが必要な場合の適切な代替策となるでしょう。
- resolver_match¶
レスポンスに対する
ResolverMatch
のインスタンスです。たとえば、次のようにレスポンスを提供するビューを検証するためにfunc
属性を使えます。# my_view here is a function based view. self.assertEqual(response.resolver_match.func, my_view) # Class-based views need to compare the view_class, as the # functions generated by as_view() won't be equal. self.assertIs(response.resolver_match.func.view_class, MyView)
指定された URL が見つからない場合、この属性にアクセスすると
Resolver404
例外が投げられます。
通常のレスポンスと同様に、HttpResponse.headers
経由でヘッダにアクセスできます。たとえば、レスポンスの content type は、response.headers['Content-Type']
を使って決定できます。
例外¶
例外を発生させるビューをテストクライアントで指定し、Client.raise_request_exception
が True
である場合、例外はテストケース内で可視化されます。その後、標準の try ... except
ブロックを使うか、assertRaises()
で例外をテストできます。
テストクライアントに可視化されない唯一の例外は、Http404
、PermissionDenied
、SystemExit
、SuspiciousOperation
です。Django はこれらの例外を内部でキャッチし、適切な HTTP レスポンスコードに変換します。これらの場合、テストでは response.status_code
をチェックできます。
Client.raise_request_exception
が False
の場合、テストクライアントはブラウザに返されることになる 500 レスポンスを返します。レスポンスには exc_info
属性があり、未処理の例外に関する情報が提供されます。
永続的なステート¶
テストクライアントはステートフルです。レスポンスがクッキーを返す場合、そのクッキーはテストクライアント内に保持され、以降すべての get()
および post()
リクエストともに送信されます。
クッキーに対する有効期限ポリシーは使えません。クッキーを期限切れにしたい場合、手動で削除するか、新たに (すべてのクッキーを削除するために) Client
を作成してください。
テストクライアントは、永続的なステート情報を保持する属性を持ちます。テスト条件の一環として、これらのプロパティにアクセスできます。
- Client.cookies¶
Python の
SimpleCookie
オブジェクトで、すべてのクライアントのクッキーの現在の値を含みます。より詳しくは、http.cookies
のドキュメントを参照してください。
- Client.session¶
辞書形式のようなオブジェクトで、セッション情報を含みます。セッションのドキュメント に網羅的情報があります。
セッションを修正し保存するためには、まず変数に格納される必要があります (新しい
SessionStore
は、この属性がアクセスされるたびに作られるためです):def test_something(self): session = self.client.session session["somekey"] = "test" session.save()
言語の設定¶
国際化とローカライズをサポートしているアプリケーションをテストする際、テストクライアントのリクエストに対して言語を設定するのがよいでしょう。その方法は、LocaleMiddleware
が有効化されているか否かによって異なります。
ミドルウェアが有効化されている場合、LANGUAGE_COOKIE_NAME
の名前でクッキーを作成することでセットすることができ、言語コードの値は次のようになります。
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fr"})
response = self.client.get("/")
self.assertEqual(response.content, b"Bienvenue sur mon site.")
もしくは、Accept-Language
HTTP ヘッダをリクエスト内に含めます。
def test_language_using_header(self):
response = self.client.get("/", headers={"accept-language": "fr"})
self.assertEqual(response.content, b"Bienvenue sur mon site.")
注釈
これらのメソッドを使用するときは、必ず各テストの最後にアクティブな言語をリセットしてください。
def tearDown(self):
translation.activate(settings.LANGUAGE_CODE)
さらなる詳細は Django はどうやって言語の優先順位を検出するのか? にあります。
ミドルウェアが有効化されていない場合、アクティブな言語は translation.override()
を使って設定できます。
from django.utils import translation
def test_language_using_override(self):
with translation.override("fr"):
response = self.client.get("/")
self.assertEqual(response.content, b"Bienvenue sur mon site.")
さらなる詳細は 明示的にアクティブな言語をセットする にあります。
カスタマイズ例¶
以下は、テストクライアントを使ったユニットテストです。
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get("/customer/details/")
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context["customers"]), 5)
提供されるテストケースのクラス¶
標準の Python ユニットテストのクラスは、unittest.TestCase
のベースクラスを拡張しています。Django は、このベースクラスのいくつかの拡張を提供します。
通常の unittest.TestCase
は、任意のサブクラスに変換できます。つまり、テストのベースクラスを unittest.TestCase
からサブクラスに変更します。すべての標準の Python のユニットテスト機能が利用可能なまま、以下の各セクションで説明するような便利な追加機能を追加できます。
SimpleTestCase
¶
以下の機能を追加する、unittest.TestCase
のサブクラスです。
便利なアサーションには以下のようなものがあります。
実行可能な関数が
特定の例外を送出する
ことをチェックする。実行可能な関数が
特定の warning をトリガーする
ことをチェックする。フォームのフィールドの
レンダリングとエラー処理
をテストする。HTML レスポンスに指定されたフラグメントが表示/非表示される
ことをテストする。テンプレートが
指定されたレスポンスのコンテンツを生成するために使われた/使われなかった
ことを検証する。2つの
URL
が等しいかどうかを検証する。HTTP
リダイレクト
がアプリによって実行されたことを検証する。2つの
HTML フラグメント
の等価性/非等価性、または包含関係 (containment)
をロバストにテストする。2つの
XML フラグメント
の等価性/非等価性をロバストにテストする。2つの
JSON フラグメント
の等価性をロバストにテストする。
編集した設定 でテストを実行する機能。
データベースクエリを作る手薄との場合、サブクラス TransactionTestCase
や TestCase
を使用してください。
- SimpleTestCase.databases¶
SimpleTestCase
はデフォルトでデータベースクエリを許可しません。各SimpleTestCase
テストはトランザクション内で実行されないため、これにより、他のテストに影響する書き込みクエリの実行を避けられます。この問題が心配でない場合は、テストクラスのdatabases
クラス属性を'__all__'
に設定することで、この振る舞いを無効化できます。
警告
SimpleTestCase
とそのサブクラス (例: TestCase
など) は、クラス横断的な初期化を実現するために setUpClass()
と tearDownClass()
に依存しています (例: 設定のオーバーライド)。これらをオーバーライドする必要があるときは、次のように super
実装を呼び出すのを忘れないでください。
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super().tearDownClass()
setUpClass()
で例外が発生した場合の Python の動作を必ず考慮してください。もし例外が起きた場合は、クラス内のテストも tearDownClass()
も実行されません。django.test.TestCase
の場合、super()
内で作成されたトランザクションがリークし、一部のプラットフォーム (macOS で報告されています) での segmentation fault を含む、さまざまな症状が起きる結果となります。setUpClass()
内での unittest.SkipTest
などの例外を意図的に発生させたい場合は、この問題を避けるために、必ず super()
を呼び出す前に実行するようにしてください。
TransactionTestCase
¶
TransactionTestCase
は SimpleTestCase
を継承し、以下のようなデータベースに特有の機能を追加しています。
Django の TestCase
クラスは、より一般的に使用される TransactionTestCase
のサブクラスで、データベース トランザクション機能を活用して、各テストの開始時にデータベースを既知の状態にリセットする処理を高速化します。しかし、この結果として、一部のデータベースの振る舞いを Django の TestCase
クラスでテストできなくなります。たとえば、select_for_update()
の使用が必要なため、コードブロックをトランザクション内で実行するテストが行えません。そのような場合には、TransactionTestCase
を使う必要があります。
TransactionTestCase
と TestCase
は、以下のように、データベースを既知の状態にリセットする方法と、コミットとロールバックの効果をテストするコードの機能を除いて同一です。
TransactionTestCase
は、テスト実行後にすべてのテーブルを truncate することでデータベースをリセットします。TransactionTestCase
はコミットとロールバックを呼び出して、これらの呼び出しのデータベースへの効果を観察できます。一方、
TestCase
は、テスト後にテーブルを truncate しません。代わりに、テスト終了時にロールバックされるデータベーストランザクション内にテストコードを含めます。これにより、テスト終了時のロールバックによりデータベースが初期状態に復元されることを保証します。
警告
ロールバックをサポートしないデータベース (例: MyISAM ストレージエンジンを使用した MySQL) 上で実行される TestCase
と、TransactionTestCase
のすべてのインスタンスは、テスト終了時にテストデータベースからすべてのデータを削除することによってロールバックされます。
アプリは データの再読み込みを確認しません。そのため、この機能が必要な場合には (たとえば、サードパーティのアプリはこれを有効化する必要があります)、TestCase
本体内で serialized_rollback = True
を設定できます。
TestCase
¶
これは、Django でテストを書く際に使われる、最も一般的なクラスです。TransactionTestCase
(および、拡張による SimpleTestCase
) を継承します。あなたの Django アプリケーションがデータベースを使用しない場合は、SimpleTestCase
を使ってください。
クラス:
2 つのネストされた
atomic()
ブロックでテストをラップします。1 つはテスト全体、もう 1 つは各テストのためです。したがって、特定のデータベーストランザクションの振る舞いをテストしたい場合は、TransactionTestCase
を使ってください。各テストの最後に、deferrable なデータベース制約をチェックします。
さらに、以下のような追加メソッドを提供します。
- classmethod TestCase.setUpTestData()[ソース]¶
上述のクラスレベルの
atomic
ブロックは、全体のTestCase
に対して一度、クラスレベルでの初期データの作成を可能にします。 この技法により、setUp()
を使うのに比べて高速なテストとなります。例:
from django.test import TestCase class MyTests(TestCase): @classmethod def setUpTestData(cls): # Set up data for the whole TestCase cls.foo = Foo.objects.create(bar="Test") ... def test1(self): # Some test using self.foo ... def test2(self): # Some other test using self.foo ...
テストがトランザクションサポートのないデータベース (たとえば MyISAM エンジンの MySQL) で実行される場合、各テストの前に
setUpTestData()
が呼ばれ、高速化のメリットはなくなります。Objects assigned to class attributes in
setUpTestData()
内でクラス属性に代入されたオブジェクトは、各テストメソッドで実行される変更から隔離するために、copy.deepcopy()
を使用したディープコピーの作成をサポートしなければなりません。
- classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False)[ソース]¶
指定されたデータベースコネクションに対する
transaction.on_commit()
コールバックをキャプチャしたコンテキストマネージャを返します。コンテキストの終了時に、キャプチャされたコールバック関数を含むリストを返します。このリストから、コールバック上のアサーションを作成したり、その副作用を起こすために呼び出したり、コミットをエミュレートしたりできます。using
は、コールバックをキャプチャするためのデータベースコネクションのエイリアスです。execute
がTrue
の場合、コンテキストマネージャの終了時に、すべてのコールバックが呼ばれます。これは、ラップされたコードブロック後のコミットをエミュレートします。例:
from django.core import mail from django.test import TestCase class ContactTests(TestCase): def test_post(self): with self.captureOnCommitCallbacks(execute=True) as callbacks: response = self.client.post( "/contact/", {"message": "I like your site"}, ) self.assertEqual(response.status_code, 200) self.assertEqual(len(callbacks), 1) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, "Contact Form") self.assertEqual(mail.outbox[0].body, "I like your site")
LiveServerTestCase
¶
LiveServerTestCase
は TransactionTestCase
とほぼ同じですが、セットアップ時に実際の Django サーバーをバックグラウンドで起動し、終了時に破棄するという追加機能が1つあります。これにより、たとえば Selenium クライアントのような Django ダミークライアント 以外の自動化されたテストクライアントを使えるようになり、ブラウザ内での一連の機能テストを実施して、実際のユーザーの行動をシミュレートできます。
ライブサーバーは localhost
をリッスンして、オペレーティングシステムが空きポートに割り当てるために使われるポート 0 にバインドします。サーバーの URL はテスト中は self.live_server_url
でアクセスできます。
LiveServerTestCase
の使用方法を紹介するために、Selenium テストを書いてみましょう。最初に、 selenium パッケージをインストールする必要があります。
$ python -m pip install "selenium >= 4.8.0"
...\> py -m pip install "selenium >= 4.8.0"
その後、LiveServerTestCase
ベースのテストをアプリのテストモジュール (例: myapp/tests.py
) に追加します。たとえば、staticfiles
アプリを使っていて、開発時に DEBUG=True
を使用したときと同じように、つまり、collectstatic
を使って静的ファイルを集める必要なく、静的ファイルがテストの実行中に配信されてほしいとします。その機能を提供する StaticLiveServerTestCase
サブクラスを使うことになります。それが必要ない場合は、django.test.LiveServerTestCase
で置き換えてください。
このテストに対するコードは、以下のようになるでしょう。
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ["user-data.json"]
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get(f"{self.live_server_url}/login/")
username_input = self.selenium.find_element(By.NAME, "username")
username_input.send_keys("myuser")
password_input = self.selenium.find_element(By.NAME, "password")
password_input.send_keys("secret")
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
最後に、次のようにテストを実行できます。
$ ./manage.py test myapp.tests.MySeleniumTests.test_login
...\> manage.py test myapp.tests.MySeleniumTests.test_login
この例では、Firefox が自動的に開き、ログインページに行き、クレデンシャルを入力して、「Log in」ボタンをクリックします。Selenium は、Firefox をインストール済みでないか、他のブラウザを使いたいときのために、他のドライバも提供します。上の例は、Selenium クライアントができることのほんの一部に過ぎません。詳細については、完全なリファレンス を確認してください。
注釈
インメモリ SQLite データベースを使用してテストを実行する場合、同じデータベース接続を 2 つのスレッドで並行して共有します。ライブサーバを実行するスレッドとテストケースを実行するスレッドです。この 2 つのスレッドが共有接続を介して同時にデータベースをクエリしないようにすることが重要です。これは、テストがランダムに失敗する可能性があるからです。そのため、2 つのスレッドが同時にデータベースにアクセスしないようにする必要があります。特に、場合によっては (たとえばリンクをクリックしたりフォームを送信したりした直後に) Selenium がレスポンスを受け取り、次のページが読み込まれたことを確認してからテストを実行する必要があるかもしれません。たとえば、レスポンスの中に <body>
という HTML タグが見つかるまで Selenium を待たせるには、下記のようにします (Selenium > 2.13 が必要です):
def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
timeout = 2
...
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
lambda driver: driver.find_element(By.TAG_NAME, "body")
)
ここで厄介なのは、特にサーバーが最初のドキュメントを生成した後に動的にHTMLを生成する最近のウェブアプリでは、「ページの読み込み」というものが実際には存在しないということです。そのため、レスポンスに <body>
があるかどうかをチェックすることは、必ずしもすべてのユースケースに適しているとは限りません。詳しくは Selenium FAQ と Selenium documentation を参照してください。
テストケースの機能¶
デフォルトのテストクライアント¶
- SimpleTestCase.client¶
django.test.*TestCase
インスタンス内のすべてのテストケースは、Django のテストクライアントにアクセスできます。このクライアントは、self.client
としてアクセスできます。このクライアントはテストごとに再作成されるため、(クッキーのような) ステートがテスト間で持ち越されることを心配する必要はありません。
つまり、各テスト内で Client
をインスタンス化する代わりに、次のようにします。
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get("/customer/details/")
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get("/customer/index/")
self.assertEqual(response.status_code, 200)
次のように self.client
を参照できます。
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get("/customer/details/")
self.assertEqual(response.status_code, 200)
def test_index(self):
response = self.client.get("/customer/index/")
self.assertEqual(response.status_code, 200)
テストクライアントをカスタムする¶
- SimpleTestCase.client_class¶
異なる Client
クラスを使用したい場合 (たとえば独自の動作を持つサブクラス)、次のように client_class
クラス属性を使ってください。
from django.test import Client, TestCase
class MyTestClient(Client):
# Specialized methods for your environment
...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
call_some_test_code()
フィクスチャのロード¶
- TransactionTestCase.fixtures¶
データベースを利用したウェブサイト向けのテストケースクラスは、データベースにデータが何もなければあまり役に立ちません。TestCase.setUpTestData()
などで ORM を使用してオブジェクトを作成すると、テストがよりリーダブルになってメンテナンス性が高まりますが、フィクスチャ を活用することもできます。
フィクスチャは、Django がどうやってデータベースにインポートするか分かっているデータのコレクションです。たとえば、サイトがユーザーアカウントを持つ場合、テスト内でデータベースに格納させるために、フェイクのユーザーアカウントのフィクスチャをセットアップできます。
フィクスチャを作成するもっとも分かりやすい方法は、manage.py dumpdata
コマンドを使うことです。これは、データベース内にすでに何らかのデータがあるという仮定に基づいています。詳細は、dumpdata のドキュメント
参照してください。
いったんフィクスチャを作成して INSTALLED_APPS
内ののどれかで fixtures
辞書に記述したら、django.test.TestCase
サブクラスで fixtures
クラス属性を指定することで、ユニットテスト内でこのフィクスチャを使えます。
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ["mammals.json", "birds"]
def setUp(self):
# Test definitions as before.
call_setup_methods()
def test_fluffy_animals(self):
# A test that uses the fixtures.
call_some_test_code()
具体的には、以下のことが起こります。
各テストの開始時、
setUp()
が実行される前に、Django はデータベースを flush し、データベースをmigrate
が呼ばれた直後の状態に戻します。そして、すべての名前付きフィクスチャがインストールされます。この例では、Django は
mammals
と名付けられたすべての JSON、birds
と名付けられたすべてのフィクスチャの順にインストールします。フィクスチャの定義とインストールに関するより詳しい情報については、フィクスチャ (fixture) トピックを参照してください。
パフォーマンス上の理由から、TestCase
がフィクスチャをロードするのは、各テストの前ではなく setUpTestData()
の前に、テストクラス全体で1回だけです。各テスト前にデータベースをクリーンアップするためにはトランザクションを使います。いずれの場合も、テストの結果が、他のテストやテストの実行順序に影響を受けないことは確実です。
デフォルトでは、フィクスチャは default
データベースにだけロードされます。複数のデータベースと TransactionTestCase.databases
を使用している場合は、フィクスチャは指定されたすべてのデータベースにロードされます。
URLconf の設定¶
アプリケーションがヴューを提供する場合、テストクライアントを使ってビューを実行するテストを含めたくなるかもしれません。しかし、エンドユーザーは、アプリケーション内で選択した任意の URL に自由にビューをデプロイできます。これが意味するのは、テストはビューが特定の URL で利用可能であるという事実に頼ることができないということです。URLconf の設定のためには、テストクラスまたはメソッドを @override_settings(ROOT_URLCONF=...)
を使用してデコレーションしてください。
マルチデータベースのサポート¶
- TransactionTestCase.databases¶
Django は、設定の DATABASES
で定義され、databases
を介して少なくとも1つのテストで参照されるデータベースそれぞれに対応するテストデータベースをセットアップします。
しかし、 Django の TestCase
の実行にかかる時間の大部分は、各テストの開始時にデータベースがクリーンであることを保証する flush
の呼び出しに消費されます。複数のデータベースがある場合、複数回のフラッシュが必要であり (各データベースに 1 回ずつ)、特に複数データベースの動作をテストする必要がないテストでは、これは時間のかかる作業です。
最適化として、Django は各テスト実行の開始時に default
データベースだけをフラッシュします。セットアップに複数のデータベースが含まれていて、全てのデータベースをクリーンにする必要があるテストがある場合、テストスイートで databases
属性を使って、追加のデータベースのフラッシュを要求できます。
例:
class TestMyViews(TransactionTestCase):
databases = {"default", "other"}
def test_index_page_view(self):
call_some_test_code()
このテストケースクラスは test_index_page_view
を実行する前に default
と other
のテストデータベースをフラッシュします。また、'__all__'
を使用すると、すべてのテストデータベースをフラッシュするように指定できます。
databases
フラグは TransactionTestCase.fixtures
がどのデータベースに読み込まれるかを制御します。デフォルトでは、フィクスチャは default
データベースにのみ読み込まれます。
databases
にないデータベースに対するクエリは、テスト間で状態が漏れるのを防ぐためにアサーションエラーを出します。
- TestCase.databases¶
デフォルトでは、TestCase
の実行中にトランザクションにラップされるのは default
データベースだけで、他のデータベースにクエリを実行しようとすると、テスト間の状態リークを防ぐためにアサーションエラーが発生します。
デフォルト以外のデータベースに対してトランザクションラッピングを要求するには、テストクラスの databases
クラス属性を使用します。
例:
class OtherDBTests(TestCase):
databases = {"other"}
def test_other_db_query(self): ...
このテストは other
データベースに対するクエリのみを許可します。 SimpleTestCase.databases
や TransactionTestCase.databases
と同様に、定数 '__all__'
を使用して、すべてのデータベースに対するクエリを許可するように指定できます。
設定のオーバーライド¶
警告
テストの設定値を一時的に変更するには、以下の関数を使います。 django.conf.settings
を直接操作しないでください。Django はそのような操作の後、元の値を復元しません。
テストの目的で、設定を一時的に変更し、テストコードを実行した後に元の値に戻すと便利なことがよくあります。このような使い方のために、 Django は settings()
という Python の標準コンテキストマネージャ (PEP 343 を参照) を提供しています:
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/accounts/login/?next=/sekrit/")
# Then override the LOGIN_URL setting
with self.settings(LOGIN_URL="/other/login/"):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
この例では、 with
ブロック内のコードに対して、 LOGIN_URL
設定をオーバーライドし、その後にその値を以前の状態にリセットします。
値のリストを含む設定の再定義は難しいことがあります。実際には、値の追加や削除で十分なことも多いです。Django には設定を簡単に変更するための modify_settings()
コンテキストマネージャがあります:
from django.test import TestCase
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
with self.modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
"remove": [
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
],
}
):
response = self.client.get("/")
# ...
各アクションに対して、値のリストまたは文字列を指定できます。値がすでにリストに存在する場合、 append
と prepend
は何もしません。同様に、値が存在しない場合、 remove
は何もしません。
テストメソッドの設定を上書きしたい場合、Django には override_settings()
デコレータがあります (PEP 318 を参照)。次のように使います:
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
@override_settings(LOGIN_URL="/other/login/")
def test_login(self):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
このデコレータは TestCase
クラスにも適用できます:
from django.test import TestCase, override_settings
@override_settings(LOGIN_URL="/other/login/")
class LoginTestCase(TestCase):
def test_login(self):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
同様に、Django には modify_settings()
デコレータがあります:
from django.test import TestCase, modify_settings
class MiddlewareTestCase(TestCase):
@modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
}
)
def test_cache_middleware(self):
response = self.client.get("/")
# ...
このデコレータはテストケースクラスにも適用できます:
from django.test import TestCase, modify_settings
@modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
}
)
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
response = self.client.get("/")
# ...
注釈
クラスが渡されると、これらのデコレータはそのクラスを直接変更して返します。変更したコピーを作成して返すわけではありません。そのため、上記の例を微調整して LoginTestCase
や MiddlewareTestCase
と異なる名前を返り値に割り当てようとすると、元のテストケースクラスがデコレータの影響を受けていることに驚くかもしれません。あるクラスに対して、 modify_settings()
は常に override_settings()
の後に適用されます。
警告
設定ファイルには、Django の内部の初期化時にだけ参照されるいくつかの設定が含まれています。設定ファイルを override_settings
で変更した場合、 django.conf.settings
モジュール経由でアクセスすれば設定は変更されますが、 Django の内部からは別の方法でアクセスはされます。これらの設定に override_settings()
や modify_settings()
を使っても、実際には期待通りの動作をすることはないでしょう。
DATABASES
の設定を変更することはお勧めしません。 CACHES
の設定を変更することは可能ですが、 django.contrib.sessions
のようなキャッシュを使用する内部機能を使用している場合は少し厄介です。例えば、キャッシュされたセッションを使用し、 CACHES
を上書きするテストでは、セッションバックエンドを再初期化する必要があります。
最後に、設定をモジュールレベルの定数としてエイリアスすることは避けてください。なぜなら、override_settings()
はそのような値には機能しないからです。それらの値はモジュールが最初にインポートされた時にのみ評価されます。
下記のように、設定が上書きされた後にその設定を削除することで、設定がない状態をシミュレートすることもできます:
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
設定を上書きする場合、アプリのコードが、キャッシュや設定が変更されても状態を保持するような機能を使用しているケースにも対処してください。Django には django.test.signals.setting_changed
シグナルがあり、設定が変更されたときに状態をクリーンアップしたり、リセットしたりするためのコールバックを登録できます。
Django 自身は、このシグナルを使って様々なデータをリセットします:
オーバーライドされた設定 |
データのリセット |
---|---|
USE_TZ, TIME_ZONE |
データベースのタイムゾーン |
TEMPLATES |
テンプレートエンジン |
FORM_RENDERER |
Default renderer |
SERIALIZATION_MODULES |
シリアライザーのキャッシュ |
LOCALE_PATHS、LANGUAGE_CODE |
デフォルトの翻訳と読み込まれた翻訳 |
STATIC_ROOT, STATIC_URL, STORAGES |
ストレージの設定 |
Resetting the default renderer when the FORM_RENDERER
setting is
changed was added.
アプリの隔離¶
- utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None)¶
ラップされたコンテキスト内で定義されたモデルを、独自の隔離された
apps
レジストリに登録します。この機能は、クラスが後できれいに削除され、名前衝突のリスクがないため、テスト用にモデルクラスを作成するときに便利です。隔離されたレジストリが含む必要があるアプリラベルは、個別の引数として渡す必要があります。
isolate_apps()
をデコレータまたはコンテキストマネージャとして使用できます。以下に例を示します。from django.db import models from django.test import SimpleTestCase from django.test.utils import isolate_apps class MyModelTests(SimpleTestCase): @isolate_apps("app_label") def test_model_definition(self): class TestModel(models.Model): pass ...
あるいは、次のようにします。
with isolate_apps("app_label"): class TestModel(models.Model): pass ...
デコレータ形式は、クラスにも適用できます。
2つのオプション引数が指定できます。
attr_name
: クラスデコレータとして使用される場合に、隔離されたレジストリに割り当てられる属性。kwarg_name
: 関数デコレータとして使用された場合に、隔離されたレジストリに渡されるキーワード引数。
モデル登録を隔離するために使用される一時的な
Apps
インスタンスは、クラスデコレータとして使用する際にattr_name
パラメータを使用して属性として取得できます。@isolate_apps("app_label", attr_name="apps") class TestModelDefinition(SimpleTestCase): def test_model_definition(self): class TestModel(models.Model): pass self.assertIs(self.apps.get_model("app_label", "TestModel"), TestModel)
あるいは、
kwarg_name
パラメータを使ってメソッドデコレータとして使用する際に、 テストメソッドの引数として指定することもできます。class TestModelDefinition(SimpleTestCase): @isolate_apps("app_label", kwarg_name="apps") def test_model_definition(self, apps): class TestModel(models.Model): pass self.assertIs(apps.get_model("app_label", "TestModel"), TestModel)
テスト用の送信トレイを空にする¶
Django のカスタムの TestCase
クラスを使うと、テストランナーは各テストケースの開始時に、テスト用のメール送信トレイの中身を消去します。
テスト中のメールサービスの詳細については、以下の Email services を参照してください。
アサーション¶
Python の通常の unittest.TestCase
クラスが assertTrue()
や assertEqual()
のようなアサーションメソッドを実装しているように、 Django のカスタム TestCase
クラスは、Web アプリケーションのテストに便利なカスタムのアサーションメソッドを多数提供しています。
これらのアサーションメソッドで出力される失敗メッセージは、 msg_prefix
引数でカスタマイズできます。この文字列は、アサーションが生成する失敗メッセージの先頭に付加されます。これにより、テストスイートで発生した失敗の場所や原因を特定しやすくなります。
- SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs)[ソース]¶
- SimpleTestCase.assertRaisesMessage(expected_exception, expected_message)
callable
の実行がexpected_exception
を引き起こし、かつその例外のメッセージにexpected_message
が含まれていることをアサートします。他の結果はすべて失敗として報告されます。これはunittest.TestCase.assertRaisesRegex()
の簡易版で、expected_message
が正規表現として扱われない点が違います。expected_exception
とexpected_message
パラメータのみが与えられた場合、テストされるコードを関数としてではなくインラインで記述できるように、コンテキストマネージャを返します。with self.assertRaisesMessage(ValueError, "invalid literal for int()"): int("a")
- SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs)[ソース]¶
- SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)
これは
SimpleTestCase.assertRaisesMessage()
に似ていますが、assertRaisesRegex()
の代わりにassertWarnsRegex()
と同様のものです。
- SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='')[ソース]¶
様々な入力に対してフォームフィールドが正しく動作することをアサートします。
- パラメータ:
fieldclass -- テストされるフィールドのクラス。
valid -- 有効な入力をそれらの期待されるクリーンな値にマッピングする辞書。
invalid -- 無効な入力を一つ以上の発生したエラーメッセージにマッピングする辞書。
field_args -- フィールドをインスタンス化するために渡される引数。
field_kwargs -- フィールドをインスタンス化するために渡されるキーワード引数。
empty_value --
empty_values
における入力の期待されるクリーンな出力。
たとえば、以下のコードは
EmailField
がa@a.com
を有効なメールアドレスとして受け入れるが、aaa
を合理的なエラーメッセージと共に拒否することをテストします:self.assertFieldOutput( EmailField, {"a@a.com": "a@a.com"}, {"aaa": ["Enter a valid email address."]} )
- SimpleTestCase.assertFormError(form, field, errors, msg_prefix='')[ソース]¶
フォーム上のフィールドが提供されたエラーのリストを発生させることをアサートします。
form
はForm
インスタンスです。フォームは バインドされている (bound である) 必要がありますが、必ずしもバリデートされている必要はありません (assertFormError()
はフォームに対して自動的にfull_clean()
を呼び出します)。field
はチェックするフォームのフィールド名です。フォームの特定のフィールドに関連付けられていないエラー
をチェックするには、field=None
を使用します。errors
は、フィールドが持つことが期待されるすべてのエラー文字列のリストです。1つのエラーのみが期待される場合は、単一のエラー文字列を渡すこともできます。つまり、errors='error message'
はerrors=['error message']
と同じです。
- SimpleTestCase.assertFormSetError(formset, form_index, field, errors, msg_prefix='')[ソース]¶
レンダリング時に
formset
が指定されたエラーリストを発生させることをアサートします。formset
はFormSet
のインスタンスです。フォームセットはバインドされている (bound である) 必要がありますが、必ずしもバリデートされている必要はありません (assertFormSetError()
は自動的にフォームセットのfull_clean()
を呼び出します)。form_index
はFormSet
内のフォームの番号です (0 から始まります)。フォームセットのフォーム以外のエラー、つまりformset.non_form_errors()
を呼び出したときに表示されるエラーをチェックするにはform_index=None
を使用します。この場合、field=None
も指定する必要があります。field
とerrors
はassertFormError()
のパラメータと同じ意味です。
- SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)[ソース]¶
response
が与えられたstatus_code
を生成し、そのcontent
にtext
が現れることをアサートします。もしcount
が指定された場合、text
はレスポンス中にcount
回出現しなければなりません。text
を HTML として扱うにはhtml
をTrue
に設定します。レスポンスの内容との比較は、一文字ずつ等しくするのではなく、HTMLのセマンティクスに基づいて行われます。ほとんどの場合、空白文字は無視され、属性の順序は重要ではありません。詳細はassertHTMLEqual()
を参照してください。Changed in Django 5.1:In older versions, error messages didn't contain the response content.
- SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)[ソース]¶
レスポンス
が指定されたstatus_code
を生成し、そのcontent
にtext
が含まれて いない ことをアサートします。text
を HTML として扱うにはhtml
をTrue
に設定します。レスポンスの内容との比較は、一文字ずつ等しくするのではなく、HTMLのセマンティクスに基づいて行われます。ほとんどの場合、空白文字は無視され、属性の順序は重要ではありません。詳細はassertHTMLEqual()
を参照してください。Changed in Django 5.1:In older versions, error messages didn't contain the response content.
- SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None)[ソース]¶
指定された名前のテンプレートがレスポンスのレンダリングに使われたことをアサートします。
response
はテストクライアント
が返すレスポンスインスタンスでなければなりません。template_name
には'admin/index.html'
のような文字列を指定します。count
引数はテンプレートがレンダリングされるべき回数を示す整数です。デフォルトはNone
で、テンプレートが1回以上レンダリングされるべきことを意味します。このようにコンテキストマネージャとして使用できます:
with self.assertTemplateUsed("index.html"): render_to_string("index.html") with self.assertTemplateUsed(template_name="index.html"): render_to_string("index.html")
- SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')[ソース]¶
指定された名前のテンプレートがレスポンスのレンダリングに使われて いない ことをアサートします。
assertTemplateUsed()
と同じようにコンテキストマネージャとして使うことができます。
- SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='')[ソース]¶
同じ名前のパラメータを除いて、クエリ文字列パラメータの順序を無視して、2つのURLが同じであることをアサートします。例えば、
/path/?x=1&y=2
は/path/?y=2&x=1
と等しいですが、/path/?a=1&a=2
は/path/?a=2&a=1
と等しくありません。
- SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)[ソース]¶
レスポンス
がstatus_code
のリダイレクトステータスを返し、expected_url
にリダイレクトされ (GET
データも含む)、最終ページをtarget_status_code
で受信したことをアサートします。リクエストが
follow
引数を使った場合、expected_url
とtarget_status_code
はリダイレクトチェーンの最終地点の url とステータスコードになります。fetch_redirect_response
がFalse
の場合、最終ページは読み込まれません。テストクライアントは外部 URL を取得できないので、expected_url
が Django アプリの一部でない場合、これは特に便利です。スキームは、2つのURL間の比較を行う際に正しく処理されます。リダイレクト先にスキームが指定されていない場合、元のリクエストのスキームが使われます。もし存在すれば、
expected_url
のスキームが比較に使われます。
- SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)[ソース]¶
文字列
html1
とhtml2
が等しいことをアサートします。比較はHTMLのセマンティクスに基づいて行われます。比較は以下のことが考慮されます:HTMLタグの前後の空白は無視されます。
すべての種類の空白は同等とみなされます。
すべての開いたタグは暗黙的に閉じられます。例えば、周囲のタグが閉じられるかHTMLドキュメントが終了したときなど。
空のタグは自己終了 (self-closing) タグと等価です。
HTML要素の属性の順序は重要ではありません。
引数を持たないブール属性 (例:
checked
) は、名前と値が同じ属性と等しいです (例を参照)。同じ文字を参照するテキスト、文字参照、およびエンティティ参照は等価です。
以下の例は有効なテストであり、どれも
AssertionError
を引き起こしません。self.assertHTMLEqual( "<p>Hello <b>'world'!</p>", """<p> Hello <b>'world'! </b> </p>""", ) self.assertHTMLEqual( '<input type="checkbox" checked="checked" id="id_accept_terms" />', '<input id="id_accept_terms" type="checkbox" checked>', )
html1
とhtml2
はHTMLを含んでいなければなりません。どちらかが解析できない場合はAssertionError
が発生します。エラー時の出力は
msg
引数でカスタマイズできます。
- SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)[ソース]¶
文字列
html1
とhtml2
が等しく ない ことをアサートします。比較はHTMLのセマンティクスに基づいて行われます。詳細はassertHTMLEqual()
を参照してください。html1
とhtml2
はHTMLを含んでいなければなりません。どちらかが解析できない場合はAssertionError
が発生します。エラー時の出力は
msg
引数でカスタマイズできます。
- SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)[ソース]¶
文字列
xml1
とxml2
が等しいことをアサートします。比較はXMLセマンティクスに基づいて行われます。assertHTMLEqual()
と同様に、比較は構文解析された内容に基づいて行われるため、構文の違いは考慮されず、意味の違いだけが考慮されます。無効なXMLがパラメータに渡された場合、両方の文字列が同じであっても、常にAssertionError
が発生します。XML宣言、ドキュメントタイプ、処理命令、コメントは無視されます。比較されるのはルート要素とその子要素だけです。
エラー時の出力は
msg
引数でカスタマイズできます。
- SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)[ソース]¶
文字列
xml1
とxml2
が等しく ない ことをアサートします。比較はXMLセマンティクスに基づいて行われます。詳細はassertXMLEqual()
を参照してください。エラー時の出力は
msg
引数でカスタマイズできます。
- SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')[ソース]¶
HTML フラグメント
needle
がhaystack
に含まれていることをアサートします。もし
count
という整数の引数が指定された場合、さらにneedle
の出現数が厳密に検証されます。ほとんどの場合、空白は無視され、属性の順序は重要ではありません。詳細は
assertHTMLEqual()
を参照してください。Changed in Django 5.1:In older versions, error messages didn't contain the
haystack
.
- SimpleTestCase.assertNotInHTML(needle, haystack, msg_prefix='')[ソース]¶
- New in Django 5.1.
Asserts that the HTML fragment
needle
is not contained in thehaystack
.ほとんどの場合、空白は無視され、属性の順序は重要ではありません。詳細は
assertHTMLEqual()
を参照してください。
- SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)[ソース]¶
JSON フラグメント
raw
とexpected_data
が等しいことをアサートします。重要な部分はjson
ライブラリに委譲されているため、通常の JSON の重要でない空白のルールが適用されます。エラー時の出力は
msg
引数でカスタマイズできます。
- SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)[ソース]¶
JSON フラグメント
raw
とexpected_data
が等しく ない ことをアサートします。詳細はassertJSONEqual()
を参照してください。エラー時の出力は
msg
引数でカスタマイズできます。
- TransactionTestCase.assertQuerySetEqual(qs, values, transform=None, ordered=True, msg=None)[ソース]¶
クエリセット
qs
が特定の値のイテラブルvalues
にマッチすることをアサートします。transform
が指定された場合、values
はqs
の各メンバにtransform
を適用したリストと比較されます。デフォルトでは、比較は順序に依存します。もし
qs
が暗黙の順序を提供しない場合、ordered
パラメータをFalse
に設定することで、比較をcollections.Counter
比較に変更できます。順序が未定義の場合 (指定されたqs
が順序付けされておらず、複数の順序付けられた値との比較の場合)、ValueError
が発生します。エラー時の出力は
msg
引数でカスタマイズできます。
- TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)[ソース]¶
func
が*args
と**kwargs
で呼び出されると、num
個のデータベースクエリが実行されることをアサートします。もし
kwargs
に"using"
キーがあれば、それをクエリの数をチェックするためのデータベースのエイリアスとして使用します。self.assertNumQueries(7, using="non_default_db")
もし
using
パラメータを持つ関数を呼び出したい場合は、呼び出しをlambda
でラップして追加のパラメータを指定できます。self.assertNumQueries(7, lambda: my_function(using=7))
コンテキストマネージャとしても使えます。
with self.assertNumQueries(2): Person.objects.create(name="Aaron") Person.objects.create(name="Daniel")
テストにタグをつける¶
テストにタグをつけることで、特定のサブセットを簡単に実行できるようになります。たとえば、速いテストや遅いテストにラベルをつけることができます。
from django.test import tag
class SampleTestCase(TestCase):
@tag("fast")
def test_fast(self): ...
@tag("slow")
def test_slow(self): ...
@tag("slow", "core")
def test_slow_but_core(self): ...
テストケースクラスにタグを付けることもできます。
@tag("slow", "core")
class SampleTestCase(TestCase): ...
サブクラスは基底クラスからタグを継承し、メソッドはそのクラスからタグを継承します。以下の例を考えてみましょう。
@tag("foo")
class SampleTestCaseChild(SampleTestCase):
@tag("bar")
def test(self): ...
SampleTestCaseChild.test
は 'slow'
, 'core'
, 'bar'
, そして 'foo'
というラベルが付けられます。
その後、どのテストを実行するか選択できます。例えば、速いテストのみを実行するには、次にようにします。
$ ./manage.py test --tag=fast
...\> manage.py test --tag=fast
または、速いテストとコアなテスト (それが遅いとしても) を実行することもできます。
$ ./manage.py test --tag=fast --tag=core
...\> manage.py test --tag=fast --tag=core
タグによってテストを除外することもできます。遅くない場合にコアテストを実行するには、次にようにします。
$ ./manage.py test --tag=core --exclude-tag=slow
...\> manage.py test --tag=core --exclude-tag=slow
test --exclude-tag
は test --tag
よりも優先されるので、テストに2つのタグがあり、片方を選択してもう片方を除外すると、テストは実行されません。
非同期コードをテストする¶
単に非同期ビューの出力をテストしたいだけであれば、標準のテストクライアントが非同期ループの中で実行するので、追加の作業は必要ありません。
しかし、Django プロジェクトで完全な非同期テストを書きたい場合、いくつかの点を考慮する必要があります。
まず、テストはテストクラスの async def
メソッドでなければなりません (非同期コンテキストを与えるため)。Django は自動的に async def
テストを検出し、独自のイベントループで実行されるようにラップします。
非同期関数からテストする場合は、非同期テストクライアントも使用する必要があります。これは django.test.AsyncClient
として、あるいは任意のテスト上で self.async_client
として利用できます。
- class AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults)[ソース]¶
AsyncClient
は同期 (通常の) テストクライアントと同じメソッドとシグネチャを持ちますが、 以下の例外があります。
初期化では、
defaults
にある任意のキーワード引数が直接 ASGI スコープに追加されます。キーワード引数
extra
として渡されるヘッダには、同期クライアントが必要とするHTTP_
プレフィックスを付けてはいけません (Client.get()
を参照してください)。例えば、HTTPのAccept
ヘッダを設定する方法を示します。>>> c = AsyncClient() >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
AsyncClient
に follow
パラメータが追加されました。
The query_params
argument was added.
AsyncClient
を使用する場合、リクエストを行うあらゆるメソッドは await にする必要があります。
async def test_my_thing(self):
response = await self.async_client.get("/some-url/")
self.assertEqual(response.status_code, 200)
非同期クライアントは同期ビューを呼び出すこともできます。Django の 非同期のリクエスト経路 を通して実行され、両方に対応しています。AsyncClient
を通して呼び出されたビューは、通常のクライアントが作成する WSGIRequest
ではなく、ASGIRequest
オブジェクトを request
として取得します。
警告
テストデコレータを使う場合、正しく動作するためには非同期対応でなければなりません。Django のビルトインデコレータは正しく動作しますが、サードパーティ製のデコレータは実行されないように見えるかもしれません (実行フローの間違った部分を「ラップ」してしまい、あなたのテストは実行されません)。
これらのデコレータを使用する必要がある場合は、テストメソッドをそれらの 内部で async_to_sync()
でデコレートするべきです。
from asgiref.sync import async_to_sync
from django.test import TestCase
class MyTests(TestCase):
@mock.patch(...)
@async_to_sync
async def test_my_thing(self): ...
メールサービス¶
Django のビューのどれかが Django のメール機能 を使ってメールを送信している場合、そのビューを使ってテストを実行するたびにメールを送信したくないでしょう。このため、Django のテストランナーは、Django が送信したメールを自動的にダミーの送信トレイにリダイレクトします。これにより、実際にメールを送信することなく、メール送信のあらゆる側面 (メッセージの送信数から各メッセージの内容まで) をテストできます。
テストランナーは、通常のメールバックエンドをテスト用バックエンドに透過的に置き換えることでこれを実現します。(心配しないでください。これは Django 以外のメール送信者、例えばあなたのマシンのメールサーバを動かしているのであれば、それには影響しません。)
- django.core.mail.outbox¶
テスト実行中、各送信メールは django.core.mail.outbox
に保存されます。これは送信された全ての EmailMessage
インスタンスのリストです。outbox
属性は、locmem
メールバックエンドを使用した場合に のみ 作成される特別な属性です。通常は django.core.mail
モジュールの一部として存在しないので、直接インポートすることはできません。以下のコードでこの属性に正しくアクセスする方法を示しています。
以下は 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")
前述の ように、テストの送信トレイは Django *TestCase
の各テストの開始時に空になります。手動で送信トレイを空にするには、mail.outbox
に空のリストを代入してください。
from django.core import mail
# Empty the test outbox
mail.outbox = []
管理コマンド¶
管理コマンドは、call_command()
関数を使用してテストできます。以下のように、出力は StringIO
インスタンスにリダイレクトできます。
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
call_command("closepoll", poll_ids=[1], stdout=out)
self.assertIn('Successfully closed poll "1"', out.getvalue())
テストのスキップ¶
unittest ライブラリは @skipIf
デコレータと @skipUnless
デコレータを提供しており、特定の条件下でテストが失敗することがわかっている場合にテストをスキップできます。
例えば、テストを成功させるために特定のオプションライブラリが必要な場合に、テストケースを @skipIf
でデコレートすることができます。そうすると、テストランナーはテストを失敗させたり完全に省略したりする代わりに、テストが実行されなかったこととその理由を報告します。
これらのテストスキップ動作を補うために、 Django はさらに 2 つのスキップデコレータを提供しています。一般的な真偽値をテストする代わりに、これらのデコレータはデータベースの機能をチェックし、データベースが特定の機能をサポートしていなければテストをスキップします。
デコレータは、データベース機能を表す文字列識別子を使用します。この文字列はデータベース接続の features クラスの属性に対応します。 django.db.backends.base.features.BaseDatabaseFeatures クラス に、テストをスキップするベースとして使用できるデータベース機能の完全なリストがあります。
指定したデータベース機能がすべてサポートされている場合は、デコレートされたテストや TestCase
をスキップします。
例えば、データベースがトランザクションをサポートしている場合、以下のテストは実行されません (例えば、PostgreSQLでは実行 されません が、MyISAMテーブルを持つMySQLでは実行されます)。
class MyTests(TestCase):
@skipIfDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass
指定したデータベース機能がサポートされていない場合は、デコレートされたテストや TestCase
をスキップします。
例えば、以下のテストはデータベースがトランザクションをサポートしている場合だけ実行されます (たとえば、PostgreSQLでは実行されますが、MyISAMテーブルを持つMySQLでは実行 されません)。
class MyTests(TestCase):
@skipUnlessDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass