テストツール

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, and extra keyword arguments passed to get(), 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.

followTrue を与えると、クライアントはすべてのリダイレクトを辿り、途中の 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)]

secureTrue に設定すると、クライアントは 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_typeapplication/json を指定すると、 data が dict、リスト、タプルの場合、 json.dumps() を使ってシリアライズされます。デフォルトでは DjangoJSONEncoder でシリアライズを行いますが、 Clientjson_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するには、キーとしてファイルフィールド名を、値としてアップロードしたいファイルへのファイルハンドルを渡すだけで十分です。例えば、フォームに nameattachment のフィールドがあり、後者が FileField の場合:

>>> c = Client()
>>> with open("wishlist.doc", "rb") as fp:
...     c.post("/customers/wishes/", {"name": "fred", "attachment": fp})
...

また、ファイルのようなオブジェクト (例えば StringIOBytesIO) をファイルハンドルとして指定することもできます。もし 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, and extra parameters acts the same as for Client.get().

POST でリクエストした URL にエンコード済みパラメータが含まれている場合には、これらのデータは request.GET データから利用できます。たとえば、次のようなリクエストを行った場合、

>>> c.post(
...     "/login/", {"name": "fred", "passwd": "secret"}, query_params={"visitor": "true"}
... )

このリクエストをハンドリングするビューでは、request.POST からはユーザー名とパスワードを取得し、request.GET からはユーザーが visitor であるかどうかを特定できます。

followTrue を与えると、クライアントはすべてのリダイレクトを辿り、途中の URL とステータスコードのタプルが、レスポンスオブジェクトの redirect_chain 属性に追加されてゆきます。

secureTrue に設定すると、クライアントは 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 a Response object. This method works just like Client.get(), including the follow, secure, headers, query_params, and extra 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_typeContent-Type ヘッダーに設定されます。

The follow, secure, headers, query_params, and extra parameters act the same as for Client.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_typeContent-Type ヘッダーに設定されます。

The follow, secure, headers, query_params, and extra parameters act the same as for Client.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, and extra parameters act the same as for Client.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_typeContent-Type ヘッダーに設定されます。

The follow, secure, headers, query_params, and extra parameters act the same as for Client.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, and extra parameters act the same as for Client.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 を生成するテンプレートをレンダリングする際に使われます。

描画されたページが複数のテンプレートを使っていた場合、contextContext オブジェクトのリストとなり、その順序はレンダリングされた順となります。

レンダリングに使われるテンプレートの数にかかわらず、[] オペレータを使ってコンテキストの値を取り出すことができます。たとえば、コンテキストの変数 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_infoNone になります。

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_exceptionTrue である場合、例外はテストケース内で可視化されます。その後、標準の try ... except ブロックを使うか、assertRaises() で例外をテストできます。

テストクライアントに可視化されない唯一の例外は、Http404PermissionDeniedSystemExitSuspiciousOperation です。Django はこれらの例外を内部でキャッチし、適切な HTTP レスポンスコードに変換します。これらの場合、テストでは response.status_code をチェックできます。

Client.raise_request_exceptionFalse の場合、テストクライアントはブラウザに返されることになる 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()
Client.asession()
New in Django 5.0.

session 属性と同様ですが、非同期コンテキストで動作します。

言語の設定

国際化とローカライズをサポートしているアプリケーションをテストする際、テストクライアントのリクエストに対して言語を設定するのがよいでしょう。その方法は、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 は、このベースクラスのいくつかの拡張を提供します。

Django ユニットテストクラスの階層 (TestCase サブクラス)

Django ユニットテストクラスの階層

通常の unittest.TestCase は、任意のサブクラスに変換できます。つまり、テストのベースクラスを unittest.TestCase からサブクラスに変更します。すべての標準の Python のユニットテスト機能が利用可能なまま、以下の各セクションで説明するような便利な追加機能を追加できます。

SimpleTestCase

class SimpleTestCase[ソース]

以下の機能を追加する、unittest.TestCase のサブクラスです。

データベースクエリを作る手薄との場合、サブクラス TransactionTestCaseTestCase を使用してください。

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

class TransactionTestCase[ソース]

TransactionTestCaseSimpleTestCase を継承し、以下のようなデータベースに特有の機能を追加しています。

Django の TestCase クラスは、より一般的に使用される TransactionTestCase のサブクラスで、データベース トランザクション機能を活用して、各テストの開始時にデータベースを既知の状態にリセットする処理を高速化します。しかし、この結果として、一部のデータベースの振る舞いを Django の TestCase クラスでテストできなくなります。たとえば、select_for_update() の使用が必要なため、コードブロックをトランザクション内で実行するテストが行えません。そのような場合には、TransactionTestCase を使う必要があります。

TransactionTestCaseTestCase は、以下のように、データベースを既知の状態にリセットする方法と、コミットとロールバックの効果をテストするコードの機能を除いて同一です。

  • TransactionTestCase は、テスト実行後にすべてのテーブルを truncate することでデータベースをリセットします。TransactionTestCase はコミットとロールバックを呼び出して、これらの呼び出しのデータベースへの効果を観察できます。

  • 一方、TestCase は、テスト後にテーブルを truncate しません。代わりに、テスト終了時にロールバックされるデータベーストランザクション内にテストコードを含めます。これにより、テスト終了時のロールバックによりデータベースが初期状態に復元されることを保証します。

警告

ロールバックをサポートしないデータベース (例: MyISAM ストレージエンジンを使用した MySQL) 上で実行される TestCase と、TransactionTestCase のすべてのインスタンスは、テスト終了時にテストデータベースからすべてのデータを削除することによってロールバックされます。

アプリは データの再読み込みを確認しません。そのため、この機能が必要な場合には (たとえば、サードパーティのアプリはこれを有効化する必要があります)、TestCase 本体内で serialized_rollback = True を設定できます。

TestCase

class 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 は、コールバックをキャプチャするためのデータベースコネクションのエイリアスです。

executeTrue の場合、コンテキストマネージャの終了時に、すべてのコールバックが呼ばれます。これは、ラップされたコードブロック後のコミットをエミュレートします。

例:

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

class LiveServerTestCase[ソース]

LiveServerTestCaseTransactionTestCase とほぼ同じですが、セットアップ時に実際の 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 FAQSelenium 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 を実行する前に defaultother のテストデータベースをフラッシュします。また、'__all__' を使用すると、すべてのテストデータベースをフラッシュするように指定できます。

databases フラグは TransactionTestCase.fixtures がどのデータベースに読み込まれるかを制御します。デフォルトでは、フィクスチャは default データベースにのみ読み込まれます。

databases にないデータベースに対するクエリは、テスト間で状態が漏れるのを防ぐためにアサーションエラーを出します。

TestCase.databases

デフォルトでは、TestCase の実行中にトランザクションにラップされるのは default データベースだけで、他のデータベースにクエリを実行しようとすると、テスト間の状態リークを防ぐためにアサーションエラーが発生します。

デフォルト以外のデータベースに対してトランザクションラッピングを要求するには、テストクラスの databases クラス属性を使用します。

例:

class OtherDBTests(TestCase):
    databases = {"other"}

    def test_other_db_query(self): ...

このテストは other データベースに対するクエリのみを許可します。 SimpleTestCase.databasesTransactionTestCase.databases と同様に、定数 '__all__' を使用して、すべてのデータベースに対するクエリを許可するように指定できます。

設定のオーバーライド

警告

テストの設定値を一時的に変更するには、以下の関数を使います。 django.conf.settings を直接操作しないでください。Django はそのような操作の後、元の値を復元しません。

SimpleTestCase.settings()[ソース]

テストの目的で、設定を一時的に変更し、テストコードを実行した後に元の値に戻すと便利なことがよくあります。このような使い方のために、 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 設定をオーバーライドし、その後にその値を以前の状態にリセットします。

SimpleTestCase.modify_settings()[ソース]

値のリストを含む設定の再定義は難しいことがあります。実際には、値の追加や削除で十分なことも多いです。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("/")
            # ...

各アクションに対して、値のリストまたは文字列を指定できます。値がすでにリストに存在する場合、 appendprepend は何もしません。同様に、値が存在しない場合、 remove は何もしません。

override_settings(**kwargs)[ソース]

テストメソッドの設定を上書きしたい場合、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/")
modify_settings(*args, **kwargs)[ソース]

同様に、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("/")
        # ...

注釈

クラスが渡されると、これらのデコレータはそのクラスを直接変更して返します。変更したコピーを作成して返すわけではありません。そのため、上記の例を微調整して LoginTestCaseMiddlewareTestCase と異なる名前を返り値に割り当てようとすると、元のテストケースクラスがデコレータの影響を受けていることに驚くかもしれません。あるクラスに対して、 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

ストレージの設定

Changed in Django 5.1:

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_exceptionexpected_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 における入力の期待されるクリーンな出力。

たとえば、以下のコードは EmailFielda@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='')[ソース]

フォーム上のフィールドが提供されたエラーのリストを発生させることをアサートします。

formForm インスタンスです。フォームは バインドされている (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 が指定されたエラーリストを発生させることをアサートします。

formsetFormSet のインスタンスです。フォームセットはバインドされている (bound である) 必要がありますが、必ずしもバリデートされている必要はありません (assertFormSetError() は自動的にフォームセットの full_clean() を呼び出します)。

form_indexFormSet 内のフォームの番号です (0 から始まります)。フォームセットのフォーム以外のエラー、つまり formset.non_form_errors() を呼び出したときに表示されるエラーをチェックするには form_index=None を使用します。この場合、 field=None も指定する必要があります。

fielderrorsassertFormError() のパラメータと同じ意味です。

SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)[ソース]

response が与えられた status_code を生成し、その contenttext が現れることをアサートします。もし count が指定された場合、 text はレスポンス中に count 回出現しなければなりません。

text を HTML として扱うには htmlTrue に設定します。レスポンスの内容との比較は、一文字ずつ等しくするのではなく、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 を生成し、その contenttext が含まれて いない ことをアサートします。

text を HTML として扱うには htmlTrue に設定します。レスポンスの内容との比較は、一文字ずつ等しくするのではなく、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_urltarget_status_code はリダイレクトチェーンの最終地点の url とステータスコードになります。

fetch_redirect_responseFalse の場合、最終ページは読み込まれません。テストクライアントは外部 URL を取得できないので、 expected_url が Django アプリの一部でない場合、これは特に便利です。

スキームは、2つのURL間の比較を行う際に正しく処理されます。リダイレクト先にスキームが指定されていない場合、元のリクエストのスキームが使われます。もし存在すれば、 expected_url のスキームが比較に使われます。

SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)[ソース]

文字列 html1html2 が等しいことをアサートします。比較はHTMLのセマンティクスに基づいて行われます。比較は以下のことが考慮されます:

  • HTMLタグの前後の空白は無視されます。

  • すべての種類の空白は同等とみなされます。

  • すべての開いたタグは暗黙的に閉じられます。例えば、周囲のタグが閉じられるかHTMLドキュメントが終了したときなど。

  • 空のタグは自己終了 (self-closing) タグと等価です。

  • HTML要素の属性の順序は重要ではありません。

  • 引数を持たないブール属性 (例: checked) は、名前と値が同じ属性と等しいです (例を参照)。

  • 同じ文字を参照するテキスト、文字参照、およびエンティティ参照は等価です。

以下の例は有効なテストであり、どれも AssertionError を引き起こしません。

self.assertHTMLEqual(
    "<p>Hello <b>&#x27;world&#x27;!</p>",
    """<p>
        Hello   <b>&#39;world&#39;! </b>
    </p>""",
)
self.assertHTMLEqual(
    '<input type="checkbox" checked="checked" id="id_accept_terms" />',
    '<input id="id_accept_terms" type="checkbox" checked>',
)

html1html2 はHTMLを含んでいなければなりません。どちらかが解析できない場合は AssertionError が発生します。

エラー時の出力は msg 引数でカスタマイズできます。

SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)[ソース]

文字列 html1html2 が等しく ない ことをアサートします。比較はHTMLのセマンティクスに基づいて行われます。詳細は assertHTMLEqual() を参照してください。

html1html2 はHTMLを含んでいなければなりません。どちらかが解析できない場合は AssertionError が発生します。

エラー時の出力は msg 引数でカスタマイズできます。

SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)[ソース]

文字列 xml1xml2 が等しいことをアサートします。比較はXMLセマンティクスに基づいて行われます。assertHTMLEqual() と同様に、比較は構文解析された内容に基づいて行われるため、構文の違いは考慮されず、意味の違いだけが考慮されます。無効なXMLがパラメータに渡された場合、両方の文字列が同じであっても、常に AssertionError が発生します。

XML宣言、ドキュメントタイプ、処理命令、コメントは無視されます。比較されるのはルート要素とその子要素だけです。

エラー時の出力は msg 引数でカスタマイズできます。

SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)[ソース]

文字列 xml1xml2 が等しく ない ことをアサートします。比較はXMLセマンティクスに基づいて行われます。詳細は assertXMLEqual() を参照してください。

エラー時の出力は msg 引数でカスタマイズできます。

SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')[ソース]

HTML フラグメント needlehaystack に含まれていることをアサートします。

もし 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 the haystack.

ほとんどの場合、空白は無視され、属性の順序は重要ではありません。詳細は assertHTMLEqual() を参照してください。

SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)[ソース]

JSON フラグメント rawexpected_data が等しいことをアサートします。重要な部分は json ライブラリに委譲されているため、通常の JSON の重要でない空白のルールが適用されます。

エラー時の出力は msg 引数でカスタマイズできます。

SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)[ソース]

JSON フラグメント rawexpected_data が等しく ない ことをアサートします。詳細は assertJSONEqual() を参照してください。

エラー時の出力は msg 引数でカスタマイズできます。

TransactionTestCase.assertQuerySetEqual(qs, values, transform=None, ordered=True, msg=None)[ソース]

クエリセット qs が特定の値のイテラブル values にマッチすることをアサートします。

transform が指定された場合、 valuesqs の各メンバに 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-tagtest --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")
    
Changed in Django 5.0:

AsyncClientfollow パラメータが追加されました。

Changed in Django 5.1:

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 クラス に、テストをスキップするベースとして使用できるデータベース機能の完全なリストがあります。

skipIfDBFeature(*feature_name_strings)[ソース]

指定したデータベース機能がすべてサポートされている場合は、デコレートされたテストや TestCase をスキップします。

例えば、データベースがトランザクションをサポートしている場合、以下のテストは実行されません (例えば、PostgreSQLでは実行 されません が、MyISAMテーブルを持つMySQLでは実行されます)。

class MyTests(TestCase):
    @skipIfDBFeature("supports_transactions")
    def test_transaction_behavior(self):
        # ... conditional test code
        pass
skipUnlessDBFeature(*feature_name_strings)[ソース]

指定したデータベース機能がサポートされていない場合は、デコレートされたテストや TestCase をスキップします。

例えば、以下のテストはデータベースがトランザクションをサポートしている場合だけ実行されます (たとえば、PostgreSQLでは実行されますが、MyISAMテーブルを持つMySQLでは実行 されません)。

class MyTests(TestCase):
    @skipUnlessDBFeature("supports_transactions")
    def test_transaction_behavior(self):
        # ... conditional test code
        pass
Back to Top