ユニットテスト—Djangoドキュメント
ユニットテスト
Djangoには、コードベースのtests
ディレクトリに独自のテストスイートが付属しています。 すべてのテストが常に合格することを確認することが当社のポリシーです。
テストスイートへのあらゆる貢献に感謝します!
Djangoテストはすべて、アプリケーションのテストにDjangoに付属のテストインフラストラクチャを使用します。 新しいテストの作成方法の説明については、テストの作成と実行を参照してください。
単体テストの実行
クイックスタート
まず、 GitHub でDjangoをフォークします。
次に、仮想環境を作成してアクティブ化します。 その方法に慣れていない場合は、寄稿チュートリアルをお読みください。
次に、フォークのクローンを作成し、いくつかの要件をインストールして、テストを実行します。
要件をインストールするには、コンピューターにインストールされていないオペレーティングシステムパッケージが必要になる可能性があります。 通常、エラーメッセージの最後の行などをWeb検索することで、インストールするパッケージを特定できます。 必要に応じて、オペレーティングシステムを検索クエリに追加してみてください。
要件のインストールに問題がある場合は、その手順をスキップできます。 オプションのテスト依存関係のインストールの詳細については、すべてのテストの実行を参照してください。 オプションの依存関係がインストールされていない場合、それを必要とするテストはスキップされます。
テストを実行するには、使用するデータベースを定義するDjango設定モジュールが必要です。 開始を支援するために、DjangoはSQLiteデータベースを使用するサンプル設定モジュールを提供して使用します。 別の設定モジュールを使用して別のデータベースでテストを実行する方法については、別の設定モジュールの使用を参照してください。
問題がありますか? 一般的な問題については、トラブルシューティングを参照してください。
toxを使用してテストを実行する
Tox は、さまざまな仮想環境でテストを実行するためのツールです。 Djangoには、ビルドサーバーがプルリクエストに対して実行するいくつかのチェックを自動化する基本的なtox.ini
が含まれています。 単体テストやその他のチェック(インポートソート、ドキュメントスペルチェッカー、コードフォーマットなど)を実行するには、 [をインストールして実行します。 X173X] Djangoソースツリーの任意の場所からのコマンド:
デフォルトでは、tox
は、SQLite、flake8
、isort
、およびドキュメントのスペルチェッカーのバンドルされたテスト設定ファイルを使用してテストスイートを実行します。 このドキュメントの他の場所に記載されているシステムの依存関係に加えて、コマンドpython3
がパス上にあり、適切なバージョンのPythonにリンクされている必要があります。 デフォルト環境のリストは次のように表示されます。
他のPythonバージョンとデータベースバックエンドのテスト
デフォルト環境に加えて、tox
は、他のバージョンのPythonおよび他のデータベースバックエンドの単体テストの実行をサポートします。 ただし、DjangoのテストスイートにはSQLite以外のデータベースバックエンドの設定ファイルがバンドルされていないため、独自のテスト設定を作成して提供する必要があります。 たとえば、PostgreSQLを使用してPython3.7でテストを実行するには次のようにします。
このコマンドは、Python 3.7仮想環境をセットアップし、Djangoのテストスイートの依存関係(PostgreSQLの依存関係を含む)をインストールし、指定された引数(この場合は--settings=my_postgres_settings
)を使用してruntests.py
を呼び出します。
このドキュメントの残りの部分では、tox
なしでテストを実行するためのコマンドを示しますが、runtests.py
に渡されるオプションは、引数リストの前に--
、上記のとおり。
Toxは、設定されている場合、DJANGO_SETTINGS_MODULE
環境変数も尊重します。 たとえば、以下は上記のコマンドと同等です。
$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py35-postgres
Windowsユーザーは以下を使用する必要があります。
...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py35-postgres
JavaScriptテストの実行
Djangoには、特定のcontribアプリの関数用の JavaScript単体テストのセットが含まれています。 JavaScriptテストは、tox
を使用してデフォルトで実行されません。これは、Node.js
をインストールする必要があり、ほとんどのパッチでは必要ないためです。 tox
を使用してJavaScriptテストを実行するには:
このコマンドは、npm install
を実行してテスト要件が最新であることを確認してから、npm test
を実行します。
django-docker-boxを使用してテストを実行する
django-docker-box を使用すると、サポートされているすべてのデータベースとPythonバージョンでDjangoのテストスイートを実行できます。 インストールと使用方法については、 django-docker-box プロジェクトページを参照してください。
別のsettingsモジュールを使用する
付属の設定モジュール(tests/test_sqlite.py
)を使用すると、SQLiteを使用してテストスイートを実行できます。 別のデータベースを使用してテストを実行する場合は、独自の設定ファイルを定義する必要があります。 contrib.postgres
のテストなど、一部のテストは特定のデータベースバックエンドに固有であり、別のバックエンドで実行するとスキップされます。
さまざまな設定でテストを実行するには、モジュールがPYTHONPATH
にあることを確認し、--settings
でモジュールに合格します。
テスト設定モジュールの:setting: `DATABASES` 設定では、次の2つのデータベースを定義する必要があります。
default
データベース。 このデータベースは、一次テストに使用するバックエンドを使用する必要があります。- エイリアスが
other
のデータベース。other
データベースは、クエリをさまざまなデータベースに送信できるかどうかをテストするために使用されます。 このデータベースは、default
と同じバックエンドを使用する必要があり、異なる名前を付ける必要があります。
SQLite以外のバックエンドを使用している場合は、データベースごとに他の詳細を提供する必要があります。
- :setting: `USER` オプションは、データベースの既存のユーザーアカウントを指定する必要があります。 そのユーザーは、テストデータベースを作成できるように、
CREATE DATABASE
を実行するためのアクセス許可が必要です。 - :setting: `PASSWORD` オプションは、指定された:setting:` USER` のパスワードを提供する必要があります。
テストデータベースは、:setting: `DATABASES` で定義されたデータベースの:setting:` NAME` 設定の値の前にtest_
を付けることで名前を取得します。 これらのテストデータベースは、テストが終了すると削除されます。
また、データベースがデフォルトの文字セットとしてUTF-8を使用していることを確認する必要があります。 データベースサーバーがデフォルトの文字セットとしてUTF-8を使用していない場合は、次の値を含める必要があります。 :setting: `CHARSET ` 該当するデータベースのテスト設定辞書にあります。
一部のテストのみを実行する
Djangoのテストスイート全体の実行には時間がかかります。たとえば、他のすべてを実行せずにすばやく実行したいテストをDjangoに追加した場合、すべてのテストの実行は冗長になる可能性があります。 コマンドラインのruntests.py
にテストモジュールの名前を追加することで、単体テストのサブセットを実行できます。
たとえば、一般的な関係と国際化についてのみテストを実行する場合は、次のように入力します。
個々のテストの名前をどのように見つけますか? tests/
を調べてください—各ディレクトリ名にはテストの名前があります。
特定のクラスのテストのみを実行する場合は、個々のテストクラスへのパスのリストを指定できます。 たとえば、i18n
モジュールのTranslationTests
を実行するには、次のように入力します。
それを超えて、次のように個別のテスト方法を指定できます。
--start-at
オプションを使用して、指定した最上位モジュールからテストを実行できます。 例えば:
--start-after
オプションを使用して、指定した最上位モジュールの後でテストを実行することもできます。 例えば:
--reverse
オプションは--start-at
または--start-after
オプションには影響しないことに注意してください。 さらに、これらのオプションはテストラベルでは使用できません。
Seleniumテストの実行
一部のテストでは、SeleniumとWebブラウザーが必要です。 これらのテストを実行するには、 selenium パッケージをインストールし、--selenium=<BROWSERS>
オプションを使用してテストを実行する必要があります。 たとえば、FirefoxとGoogleChromeがインストールされている場合:
使用可能なブラウザーのリストについては、 selenium.webdriver パッケージを参照してください。
--selenium
を指定すると、--tags=selenium
が自動的に設定され、セレンを必要とするテストのみが実行されます。
一部のブラウザ(例: ChromeまたはFirefox)はヘッドレステストをサポートしており、より高速で安定しています。 --headless
オプションを追加して、このモードを有効にします。
すべてのテストを実行する
テストの完全なスイートを実行する場合は、いくつかの依存関係をインストールする必要があります。
- argon2-cffi 16.1.0+
- asgiref 3.2+(必須)
- bcrypt
- docutils
- geoip2
- jinja2 2.7+
- numpy
- 枕 4.2.0+
- PyYAML
- pytz (必須)
- pywatchman
- setuptools
- memcached に加えて、でサポートされているPythonバインディング
- gettext ( Windows上のgettext )
- セレン
- sqlparse 0.2.2+(必須)
- tblib 1.5.0+
これらの依存関係は、Djangoソースツリーのtests/requirements
ディレクトリ内の pip要件ファイルにあり、次のようにインストールします。
インストール中にエラーが発生した場合は、システムに1つ以上のPythonパッケージの依存関係がない可能性があります。 失敗したパッケージのドキュメントを参照するか、発生したエラーメッセージを使用してWebを検索してください。
oracle.txt
、mysql.txt
、またはpostgres.txt
を使用して、選択したデータベースアダプタをインストールすることもできます。
memcachedキャッシュバックエンドをテストする場合は、memcachedインスタンスを指す:setting: `CACHES` 設定も定義する必要があります。
GeoDjangoテストを実行するには、空間データベースをセットアップし、Geospatialライブラリをインストールする必要があります。
これらの各依存関係はオプションです。 それらのいずれかが欠落している場合、関連するテストはスキップされます。
一部の自動リロードテストを実行するには、 Watchman サービスをインストールする必要があります。
コードカバレッジ
寄稿者は、追加のテストが必要な領域を特定するために、テストスイートでカバレッジを実行することをお勧めします。 カバレッジツールのインストールと使用については、テストコードカバレッジで説明されています。
正確な統計を取得するには、カバレッジを単一のプロセスで実行する必要があります。 標準のテスト設定を使用してDjangoテストスイートでカバレッジを実行するには:
カバレッジを実行した後、次を実行してhtmlレポートを生成します。
Djangoテストのカバレッジを実行する場合、含まれている.coveragerc
設定ファイルは、レポートの出力ディレクトリとしてcoverage_html
を定義し、結果に関係のないいくつかのディレクトリ(テストコードまたはに含まれている外部コード)も除外します。 Django)。
投稿アプリ
contribアプリのテストは、tests/
ディレクトリ、通常は<app_name>_tests
の下にあります。 たとえば、contrib.auth
のテストはtests/auth_tests
にあります。
トラブルシューティング
masterブランチでテストスイートがハングするか、エラーが表示される
でサポートされているPythonバージョンの最新のポイントリリースがあることを確認してください。以前のバージョンには、テストスイートが失敗したり、ハングしたりする可能性のあるバグが頻繁にあるためです。
macOS (High Sierra以降のバージョン)では、次のメッセージがログに記録される場合があります。その後、テストがハングします。
objc[42074]: +[__NSPlaceholderDate initialize] may have been in progress in
another thread when fork() was called.
これを回避するには、OBJC_DISABLE_INITIALIZE_FORK_SAFETY
環境変数を設定します。次に例を示します。
$ OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ./runtests.py
または、export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
をシェルのスタートアップファイルに追加します(例: ~/.profile
)。
UnicodeEncodeErrorでの多くのテストの失敗
locales
パッケージがインストールされていない場合、一部のテストはUnicodeEncodeError
で失敗します。
たとえば、Debianベースのシステムでは、次のコマンドを実行することでこれを解決できます。
$ apt-get install locales
$ dpkg-reconfigure locales
シェルのロケールを設定することで、macOSシステムでこれを解決できます。
$ export LANG="en_US.UTF-8"
$ export LC_ALL="en_US.UTF-8"
locale
コマンドを実行して、変更を確認します。 必要に応じて、これらのエクスポートコマンドをシェルの起動ファイルに追加します(例: ~/.bashrc
(Bashの場合))、それらを再入力する必要がないようにします。
組み合わせてのみ失敗するテスト
単独で実行したときにテストに合格したが、スイート全体で失敗した場合は、問題の分析に役立つツールがいくつかあります。
runtests.py
の--bisect
オプションは、失敗したテストを実行し、各反復で一緒に実行されるテストセットを半分にします。これにより、多くの場合、関連する可能性のある少数のテストを特定できます。失敗。
たとえば、それ自体で機能する失敗したテストがModelTest.test_eq
であるとすると、次を使用します。
与えられたものに干渉するテストを決定しようとします。 最初に、テストはテストスイートの前半で実行されます。 障害が発生した場合、テストスイートの前半は2つのグループに分割され、各グループは指定されたテストで実行されます。 テストスイートの前半で障害が発生しなかった場合、テストスイートの後半は指定されたテストで実行され、前述のように適切に分割されます。 このプロセスは、失敗したテストのセットが最小化されるまで繰り返されます。
--pair
オプションは、スイートの他のすべてのテストと一緒に指定されたテストを実行し、別のテストに失敗の原因となる副作用があるかどうかを確認できるようにします。 そう:
test_eq
をすべてのテストラベルとペアにします。
--bisect
と--pair
の両方で、どちらのケースが失敗の原因であるかがすでに疑われる場合は、後にさらにテストラベルを指定することにより、テストを相互分析に制限できます。最初の1つ:
--reverse
オプションを使用して、テストのセットを逆に実行して、テストを別の順序で実行しても問題が発生しないことを確認することもできます。
テスト中に実行されるSQLクエリの確認
失敗したテストで実行されているSQLを調べたい場合は、--debug-sql
オプションを使用して SQLロギングをオンにすることができます。 これを--verbosity=2
と組み合わせると、すべてのSQLクエリが出力されます。
テスト失敗の完全なトレースバックを確認する
デフォルトでは、テストはコアごとに1つのプロセスと並行して実行されます。 ただし、テストを並行して実行すると、テストの失敗に対して切り捨てられたトレースバックのみが表示されます。 この動作は、--parallel
オプションで調整できます。
この目的でDJANGO_TEST_PROCESSES
環境変数を使用することもできます。
テストを書くためのヒント
モデル登録の分離
グローバル apps レジストリの汚染を回避し、不要なテーブルの作成を防ぐには、テストメソッドで定義されたモデルを一時的なApps
インスタンスにバインドする必要があります。
from django.apps.registry import Apps
from django.db import models
from django.test import SimpleTestCase
class TestModelDefinition(SimpleTestCase):
def test_model_definition(self):
test_apps = Apps(['app_label'])
class TestModel(models.Model):
class Meta:
apps = test_apps
...
- django.test.utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None)
このパターンには多くの定型文が含まれるため、Djangoは isolate_apps()デコレーターを提供します。 これは次のように使用されます:
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
class TestModelDefinition(SimpleTestCase):
@isolate_apps('app_label')
def test_model_definition(self):
class TestModel(models.Model):
pass
...
設定app_label
明示的な app_label を使用せずにテストメソッドで定義されたモデルには、テストクラスが配置されているアプリのラベルが自動的に割り当てられます。
isolate_apps()インスタンスのコンテキスト内で定義されたモデルが正しくインストールされていることを確認するには、ターゲットのapp_label
のセットを引数として渡す必要があります。
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
class TestModelDefinition(SimpleTestCase):
@isolate_apps('app_label', 'other_app_label')
def test_model_definition(self):
# This model automatically receives app_label='app_label'
class TestModel(models.Model):
pass
class OtherAppModel(models.Model):
class Meta:
app_label = 'other_app_label'
...
デコレータはクラスにも適用できます。
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
@isolate_apps('app_label')
class TestModelDefinition(SimpleTestCase):
def test_model_definition(self):
class TestModel(models.Model):
pass
...
モデル登録を分離するために使用される一時的なApps
インスタンスは、attr_name
パラメーターを使用して、クラスデコレーターとして使用されるときに属性として取得できます。
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
@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
パラメーターを使用してメソッドデコレーターとして使用する場合のテストメソッドの引数として、次のようにします。
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
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)