テストを書いて実行する¶
参考
ドキュメントは2つの大きなセクションに分けられます。前半のパートでは、Django でのテストの書き方を説明します。後半では、テストの実行の仕方について説明します。
テストを書く¶
Django のユニットテストには、Python スタンダードライブラリのモジュール、unittest
を使用します。このモジュールは、テストをクラスベースのアプローチで定義します。
次の例では、 unittest.TestCase
のサブクラスである django.test.TestCase
から、テスト用の新しいサブクラスを作っています。各テストをトランザクションの内側で実行することで、独立性を実現しています。
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
def setUp(self):
Animal.objects.create(name="lion", sound="roar")
Animal.objects.create(name="cat", sound="meow")
def test_animals_can_speak(self):
"""Animals that can speak are correctly identified"""
lion = Animal.objects.get(name="lion")
cat = Animal.objects.get(name="cat")
self.assertEqual(lion.speak(), 'The lion says "roar"')
self.assertEqual(cat.speak(), 'The cat says "meow"')
テストを実行する と、テストユーティリティのデフォルトの動作として、ファイル名が test
で始まるファイル内のすべてのテストケースクラス (つまり unittest.TestCase
のサブクラス) を見つけ、それらのテストケースクラスから自動的にテストスイートを構築し、そのテストスイートを実行します。
unittest
の詳細については、Python のドキュメントを読んでください。
どこにテストを書くべき?
デフォルトの startapp
テンプレートは、新しいアプリケーション内に tests.py
ファイルを作成します。テストの数が少ないうちは、ここに書くのがいいかもしれません。しかし、テストスイートが大きくなってきたら、テストを複数のパッケージに再構成して、test_models.py
、 test_views.py
、 test_forms.py
などの異なるサブモジュールに分離すると良いでしょう。ファイル名には、ちゃんと組織的な命名規則になっていれば、自由に好きな名前をつけて構いません。
Django テストランナーを使って再利用可能なアプリケーションをテストする も参照してください。
警告
作成したテストが、データの新規作成やモデルのクエリなどのデータベースアクセスを必要とするときは、unittest.TestCase
ではなく、 django.test.TestCase
のサブクラスを作るようにしてください。
unittest.TestCase
を使えば、各テストでデータベースのトランザクションとフラッシュに必要な実行コストを避けることができます。しかし、データベースと相互作用するテストの場合、テストランナーがテストを実行する順番によっては、異なる動作をすることがあります。そのため、孤立した環境では成功するテストユニットでも、一連のテストスイートの中で実行した時には失敗してしまうという状況が発生することがあります。
テストの実行¶
テストが書けたら、プロジェクトの manage.py
ユーティリティの test
コマンドでテストが実行できます。
$ ./manage.py test
テストの探索方法は、unittest モジュールの ビルトインのテスト探索 にもとづきます。デフォルトでは、カレントディレクトリにある test*.py
という名前の全てのファイルからテストを探し出します。
/manage.py test
に好きな数の「テストラベル」を与えることで、特定のテストを指定することもできます。各テストラベルには、パッケージ、モジュール、 TestCase
サブクラス、テストメソッドへのドット区切りの Python パスを指定します。たとえば、以下のように指定します。
# Run all the tests in the animals.tests module
$ ./manage.py test animals.tests
# Run all the tests found within the 'animals' package
$ ./manage.py test animals
# Run just one test case class
$ ./manage.py test animals.tests.AnimalTestCase
# Run just one test method
$ ./manage.py test animals.tests.AnimalTestCase.test_animals_can_speak
ディレクトリ下に置かれたテストを探索するために、ディレクトリのパスを指定することもできます。
$ ./manage.py test animals/
-p
(または --pattern
) オプションを使って、カスタムのファイル名のパターンマッチを指定すれば、テストファイルの名前が test*.py
というパターンとは違っていても実行できます。
$ ./manage.py test --pattern="tests_*.py"
テストの実行中に Ctrl-C
を押すと、テストランナーは現在実行中のテストが完了するのを待って、gracefully にテストを終了します。graceful な終了では、テストランナーは失敗したテストの詳細を出力し、実行したテストの数と、エラーおよび失敗したテストの数をレポートし、通常通りにテストデータベースを破棄します。そのため、 Ctrl-C
を押すのは、たとえば、 --failfast
オプションを付けるのを忘れて、一部のテストが予期せず失敗して、すべてのテストが終わるのを待たずにすぐにその失敗の詳細を知りたいような場合に大変役に立ちます。
現在実行中のテストの終了も待ちたくないときは、もう一度 Ctrl-C
を押すことで、テストを graceful ではなく、すぐに強制終了できます。その場合、強制終了前に実行していたテストの詳細はリポートされず、実行中に作られたテストデータベースも破棄されません。
警告を有効にしてテストする
python -Wa manage.py test
というように、Python の警告表示を有効にしてテストを実行するのは良い考えです。-Wa
フラグをセットすると、Python に deprecation 警告を表示するように伝えます。Django などの Python ライブラリは、機能の廃止を知らせるフラグとして、この警告を利用しています。また、この機能は、厳密には間違いではないがあまり良くないコードを知らせてくれることがあるので、実装を改善できることがあります。
test データベース¶
データベースを必要とするテスト (すなわち、モデルテスト) には、"実際の" (production) 環境のデータベースは使用しません。代わりに、テスト用の空のデータベースを用意します。
テストが成功したかどうかにかかわらず、すべてのテストの実行が終わった時点で、テストデータベースは破棄されます。
test --keepdb
オプションを指定すれば、テストデータベースの破棄を防ぐことができます。これにより、複数回テストを実行しても、テストデータベースを保存できます。データベースが存在しないときは、最初に新しく作成され、そしてデータベースが最新の状態になるように、マイグレーションが順番に実行されます。
前のセクションで説明したように、テストの実行が強制的に中断された場合、テストデータベースが破棄されない可能性があります。次の実行では、データベースを再利用するか破棄するかを尋ねられるでしょう。test --noinput
オプションを使用すると、プロンプトを抑制し、データベースを自動的に破棄できます。このオプションは、たとえばタイムアウトでテスト中断される可能性がある継続的インテグレーションサーバー上でのテストの実行時などに役に立ちます。
テストデータベースのデフォルトの名前は、 DATABASES
設定内の各 NAME
の値の前に test_
を付けたものになります。SQLite を使っているときは、デフォルトでは、テストにはインメモリのデータベースを使います (つまり、データベースはメモリ内に作成されるため、ファイルシステムへのアクセスを完全になくすことができるのです!)。設定の DATABASES
内の TEST
ディクショナリには、テストデータベースに対するいろいろな設定を書くことができます。例えば、別のデータベース名を指定したければ、 TEST
ディクショナリの NAME
に、 DATABASES
の中から好きなデータベースを選んで指定できます。
PostgreSQL では、 USER
が、ビルトインの postgres
データベースへの読み取りアクセス権も持っている必要があります。
テストランナーの使うデータベースは、独立したデータベースだけでなく、設定ファイルで指定した通りのデータベースを使用させることもできます: ENGINE
, USER
, HOST
, などです。テストデータベースは、USER
で指定されたユーザによって作成されるため、そのユーザアカウントがシステム上で新しくデータベースを作成できる権限を持っている必要があります。
テストデータベースの文字エンコーディングに対するきめ細かい対応をするために、CHARSET
TEST オプションを使用してください。MySQL を使用している場合は、COLLATION
オプションを使用してテストデータベースが使用する特別な照合順序をコントロールできます。 これらおよびより進歩的な設定の詳細については、設定のドキュメント を参照してください。
SQLite で SQLite インメモリデータベースを使用する場合、 共有キャッシュ が有効になるため、スレッド間でデータベースを共有することが可能なテストを書くことができます。
テストの実行中に本番データベースからデータを見つけるには?
モジュールのコンパイル時にデータベースへのアクセスを試みると、 テスト用データベースがセットアップされる前にアクセスが発生し、予期しない結果になる可能性があります。例えば、モジュールレベルのコードにデータベースクエリがあり、実際のデータベースが存在する場合、実稼働データがテストを汚染する可能性があります。いかなる場合でも、 コードにこのようなインポート時のデータベースクエリを含めるのは良くないことです 。そのようなことをしないようにコードを書き直してください。
ready()
の実装をカスタマイズする場合にも同じことが言えます。
参考
応用的なマルチデータベースのテスト トピック も参考にしてください。
テストの実行順序¶
すべての TestCase
コードがクリーンなデータベースで実行されることを保証するために、Django のテストランナーは次の方法でテストの実行順序を決定します。
すべての
TestCase
サブクラスが最初に実行されます。そして、他のすべての Django ベースのテスト (
SimpleTestCase
をベースとしたテストケースクラス、TransactionTestCase
を含む) が、特定の順序を保証も強制もしない状態で実行されます。最後に、その他の
unittest.TestCase
テスト (doctests を含む) が実行されます。このテストの中には、データベースを変更し、そのまま元の状態に戻さないようなテストがあることもあります。
注釈
この新しいテスト順序は、テストケース順序の予期しない依存関係を明らかにするかもしれません。これは、TransactionTestCase
によってデータベース内に記述された宣言に依存する doctests のケースで、これらは独立的に実行できるように修正する必要があります。
注釈
テストを読み込む際に検出された失敗は、上記のすべての前に配置され、迅速にフィードバックされます。これには、テストモジュールが見つからなかったり、構文エラーのために読み込めなかったりしたものが含まれます。
test --shuffle
と --reverse
オプションを使用すると、グループ内の実行順序をランダムにしたり逆にしたりすることができます。これは、テストが互いに独立していることを保証するのに役立ちます。
ロールバックのエミュレーション¶
マイグレーションで呼び出されるあらゆる初期データは、TestCase
テスト内のみで有効で、 TransactionTestCase
内では無効です。加えて、トランザクションがサポートされるバックエンドのみで有効です (最も重要な例外は MyISAM です)。これは、LiveServerTestCase
や StaticLiveServerTestCase
といった TransactionTestCase
に依存するテストに関しても同様です。
Djangoは、 TestCase
や TransactionTestCase
の本文で serialized_rollback
オプションを True
に設定することで、テストケースごとにデータを再読み込みできますが、これによりテストスイートの実行速度が約3倍遅くなることに注意してください。
サードパーティのアプリや MyISAM に対して開発する場合は、通常この設定が必要です。しかし、独自のプロジェクトをトランザクションデータベースに対して開発する場合は、ほとんどのテストで TestCase
を使用するはずです。したがって、この設定は不要です。
初期シリアライズは通常、非常に高速ですが、このプロセスからいくつかのアプリを除外してテストの実行を少しでも高速にしたい場合は、それらのアプリを TEST_NON_SERIALIZED_APPS
に追加します。
シリアライズされたデータが二度読み込まれるのを防ぐために、 serialized_rollback=True
を設定すると、テストデータベースを flush するときに post_migrate
シグナルを無効にします。
その他のテストに関する条件¶
設定ファイルで指定した DEBUG
の値にかかわらず、Django のテストは DEBUG
=False を指定したものとして実行されます。これは、表示されるコードの出力が、実際の環境設定で見られるものと同じになるようにするためです。
各テスト後にキャッシュはクリアされません。そのため、manage.py test fooapp
を実行すると、テストで挿入されたデータが実働環境のシステムのキャッシュに残り続ける可能性があります。データベースの場合と違い、別の「テスト用のキャッシュ」が使われないためです。ただし、この動作は将来 変更される可能性があります 。
テストの出力を理解する¶
テストを実行すると、テストランナーが用意したたくさんのメッセージが表示されます。表示するメッセージの詳細レベルは、コマンドラインで verbosity
オプションを指定することで、自由にコントロールできます。
Creating test database...
Creating table myapp_animal
Creating table myapp_mineral
このメッセージは、テストランナーが前のセクションで説明したテスト用のデータベースを作成していることを表しています。
テスト用データベースが作成されると、Django はテストを実行します。すべてのテストが成功すれば、次のようなメッセージが表示されるはずです。
----------------------------------------------------------------------
Ran 22 tests in 0.221s
OK
しかし、もしテストが失敗した場合には、失敗したテストとその詳細が表示されます。
======================================================================
FAIL: test_was_published_recently_with_future_poll (polls.tests.PollMethodTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/dev/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_poll
self.assertIs(future_poll.was_published_recently(), False)
AssertionError: True is not False
----------------------------------------------------------------------
Ran 1 test in 0.003s
FAILED (failures=1)
このエラー出力の詳しい説明は、このドキュメントの範囲外ですが、極めて直感的に理解できるものです。詳しく知りたければ、Python の unittest
ライブラリのドキュメントを読んでみてください。
失敗したテストの数に関わらず(失敗の原因がエラー、アサーションの失敗、予期しない成功のいずれであっても)、テストランナーのスクリプトから返ってくる終了コードは 1 であることに注意してください。すべてのテストが成功すれば、0 が返ります。この特徴は、別のシェルスクリプトの中でテストランナースクリプトを実行するときに、成功したかどうかの情報が必要な時に役に立ちます。
テストのスピードアップ¶
テストの並列実行¶
各テストが適切に独立性を保ったものであれば、マルチコアのハードウェア上でテストを並列実行することでスピートアップさせることができます。詳しくは test --parallel
を読んでください。
パスワードのハッシュ生成¶
デフォルトのパスワードのハッシュ生成器は、設計上、時間のかかるものになっています。テストの中で多数のユーザーを認証する必要がある場合、カスタムの設定ファイルを用意して、PASSWORD_HASHERS
設定に、より高速なハッシュ生成アルゴリズムを設定すると良いでしょう。
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher",
]
PASSWORD_HASHERS
には、必要なハッシュアルゴリズムが複数あっても、追加しておくことを忘れないようにしてください。
テストデータベースを保存する¶
test --keepdb
オプションで、テスト間でテストデータベースを保存できます。テスト実行の際、データベース作成および破棄にかかる時間を大幅に短縮できます。
メディアファイルに対するディスクアクセスを回避する¶
InMemoryStorage
は、メディアファイルに対するディスクアクセスを避けるための便利な手段です。すべてのデータはメモリ内にとどまり、テスト実行の後に破棄されます。