データベースを設定せずにdjangoユニットテストを書く可能性はありますか?データベースの設定を必要としないビジネスロジックをテストしたい。また、dbのセットアップは高速ですが、状況によっては必要ない場合もあります。
データベースを設定せずにdjangoユニットテストを書く可能性はありますか?データベースの設定を必要としないビジネスロジックをテストしたい。また、dbのセットアップは高速ですが、状況によっては必要ない場合もあります。
回答:
DjangoTestSuiteRunnerをサブクラス化して、通過するsetup_databasesおよびteardown_databasesメソッドをオーバーライドできます。
新しい設定ファイルを作成し、作成した新しいクラスにTEST_RUNNERを設定します。次に、テストを実行するときに、-settingsフラグを使用して新しい設定ファイルを指定します。
これが私がしたことです:
次のようなカスタムテストスーツランナーを作成します。
from django.test.simple import DjangoTestSuiteRunner
class NoDbTestRunner(DjangoTestSuiteRunner):
""" A test runner to test without database creation """
def setup_databases(self, **kwargs):
""" Override the database creation defined in parent class """
pass
def teardown_databases(self, old_config, **kwargs):
""" Override the database teardown defined in parent class """
pass
カスタム設定を作成します。
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
テストを実行しているときに、--settingsフラグを新しい設定ファイルに設定して、次のように実行します。
python manage.py test myapp --settings='no_db_settings'
更新:2018年4月
Django 1.8以降、モジュールはに移動されました。django.test.simple.DjangoTestSuiteRunner
'django.test.runner.DiscoverRunner'
詳細については、カスタムテストランナーに関する公式ドキュメントセクションをご覧ください。
--testrunner
オプションを使用してコマンドラインで新しいTestRunnerを指定できます。
一般に、アプリケーションのテストは2つのカテゴリに分類できます
Djangoは単体テストと統合テストの両方をサポートしています。
単体テスト。データベースをセットアップして破棄する必要はありません。これらはSimpleTestCaseから継承する必要があります。
from django.test import SimpleTestCase
class ExampleUnitTest(SimpleTestCase):
def test_something_works(self):
self.assertTrue(True)
統合の場合、テストケースはTestCaseから継承し、TransactionTestCaseから継承します。これにより、各テストを実行する前にデータベースをセットアップして破棄します。
from django.test import TestCase
class ExampleIntegrationTest(TestCase):
def test_something_works(self):
#do something with database
self.assertTrue(True)
この戦略により、データベースにアクセスするテストケースに対してのみデータベースが作成および破棄されるため、テストがより効率的になります。
から django.test.simple
warnings.warn(
"The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
"use django.test.runner.DiscoverRunner instead.",
RemovedInDjango18Warning)
したがって、のDiscoverRunner
代わりにオーバーライドしてくださいDjangoTestSuiteRunner
。
from django.test.runner import DiscoverRunner
class NoDbTestRunner(DiscoverRunner):
""" A test runner to test without database creation/deletion """
def setup_databases(self, **kwargs):
pass
def teardown_databases(self, old_config, **kwargs):
pass
そのように使用してください:
python manage.py test app --testrunner=app.filename.NoDbTestRunner
私はメソッドから継承django.test.runner.DiscoverRunner
し、run_tests
メソッドにいくつか追加することを選択しました。
最初の追加では、dbのセットアップが必要かどうかを確認し、dbが必要なsetup_databases
場合に通常の機能を開始できるようにします。2番目の追加により、メソッドの実行が許可されてteardown_databases
いれば、通常の実行setup_databases
が可能になります。
私のコードは、から継承するdjango.test.TransactionTestCase
(したがってdjango.test.TestCase
)TestCaseでデータベースを設定する必要があると想定しています。Django docsが言うので私はこの仮定をしました:
... ORMのテストまたは使用など、他のより複雑でヘビー級のDjango固有の機能が必要な場合は、代わりにTransactionTestCaseまたはTestCaseを使用する必要があります。
https://docs.djangoproject.com/en/1.6/topics/testing/tools/#django.test.SimpleTestCase
from django.test import TransactionTestCase
from django.test.runner import DiscoverRunner
class MyDiscoverRunner(DiscoverRunner):
def run_tests(self, test_labels, extra_tests=None, **kwargs):
"""
Run the unit tests for all the test labels in the provided list.
Test labels should be dotted Python paths to test modules, test
classes, or test methods.
A list of 'extra' tests may also be provided; these tests
will be added to the test suite.
If any of the tests in the test suite inherit from
``django.test.TransactionTestCase``, databases will be setup.
Otherwise, databases will not be set up.
Returns the number of tests that failed.
"""
self.setup_test_environment()
suite = self.build_suite(test_labels, extra_tests)
# ----------------- First Addition --------------
need_databases = any(isinstance(test_case, TransactionTestCase)
for test_case in suite)
old_config = None
if need_databases:
# --------------- End First Addition ------------
old_config = self.setup_databases()
result = self.run_suite(suite)
# ----------------- Second Addition -------------
if need_databases:
# --------------- End Second Addition -----------
self.teardown_databases(old_config)
self.teardown_test_environment()
return self.suite_result(suite, result)
最後に、次の行をプロジェクトのsettings.pyファイルに追加しました。
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
ここで、dbに依存しないテストのみを実行すると、私のテストスイートは桁違いに速く実行されます!:)
更新:サードパーティツールの使用については、この回答もご覧くださいpytest
。
@Cesarは正しいです。誤って実行した後./manage.py test --settings=no_db_settings
アプリ名を指定せずにを、開発データベースが消去されました。
より安全な方法で、同じを使用しますNoDbTestRunner
が、以下と組み合わせて使用しますmysite/no_db_settings.py
。
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
# Use an alternative database as a safeguard against accidents
DATABASES['default']['NAME'] = '_test_mysite_db'
_test_mysite_db
外部データベースツールを使用して呼び出されるデータベースを作成する必要があります。次に、次のコマンドを実行して、対応するテーブルを作成します。
./manage.py syncdb --settings=mysite.no_db_settings
Southを使用している場合は、次のコマンドも実行します。
./manage.py migrate --settings=mysite.no_db_settings
OK!
次の方法で、ユニットテストを非常に高速(かつ安全)に実行できるようになりました。
./manage.py test myapp --settings=mysite.no_db_settings
NoDbTestRunnerを「安全」にするために設定を変更する代わりに、現在のデータベース接続を閉じ、設定と接続オブジェクトから接続情報を削除するNoDbTestRunnerの変更バージョンを次に示します。私のために働く、それに依存する前にあなたの環境でそれをテストする:)
class NoDbTestRunner(DjangoTestSuiteRunner):
""" A test runner to test without database creation """
def __init__(self, *args, **kwargs):
# hide/disconnect databases to prevent tests that
# *do* require a database which accidentally get
# run from altering your data
from django.db import connections
from django.conf import settings
connections.databases = settings.DATABASES = {}
connections._connections['default'].close()
del connections._connections['default']
super(NoDbTestRunner,self).__init__(*args,**kwargs)
def setup_databases(self, **kwargs):
""" Override the database creation defined in parent class """
pass
def teardown_databases(self, old_config, **kwargs):
""" Override the database teardown defined in parent class """
pass
__getitem__
なくなったようです。使用connections._connections.default
オブジェクトにアクセスするためです。
別の解決策はunittest.TestCase
、Djangoのテストクラスの代わりにテストクラスを単に継承させることです。Djangoのドキュメント(https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests)には、これに関する次の警告が含まれています。
unittest.TestCaseを使用すると、トランザクションで各テストを実行してデータベースをフラッシュするコストを回避できますが、テストがデータベースと対話する場合、その動作はテストランナーが実行する順序によって異なります。これは、単体テストで実行すると合格し、スイートで実行すると失敗する単体テストにつながる可能性があります。
ただし、テストでデータベースを使用しない場合、この警告は関係ありません。トランザクションで各テストケースを実行する必要がないという利点を享受できます。
私のWebホストでは、Web GUIからのデータベースの作成と削除のみが許可されているため、実行しようとすると「テストデータベースの作成中にエラーが発生しました:権限が拒否されました」というエラーが表示されました python manage.py test
。
django-admin.pyに--keepdbオプションを使用したいと思っていましたが、Django 1.7以降ではサポートされていないようです。
私がやったことは... / django / db / backends / creation.pyのDjangoコード、特に_create_test_db関数と_destroy_test_db関数を変更することでした。
以下のために_create_test_db
私がコメントアウトcursor.execute("CREATE DATABASE ...
行をし、それを置き換えるpass
ので、try
ブロックが空ではないでしょう。
以下のために_destroy_test_db
私はコメントアウトcursor.execute("DROP DATABASE
-ブロック内の別のコマンドが(すでにあったので、私は何もそれを交換する必要はありませんでしたtime.sleep(1)
)。
その後、テストは問題なく実行されました。ただし、通常のデータベースのtest_バージョンを個別に設定しました。
もちろん、これは素晴らしいソリューションではありません。Djangoをアップグレードすると破損するためですが、virtualenvを使用しているため、Djangoのローカルコピーがあったため、少なくとも新しいバージョンにアップグレードするかどうかを制御できます。
言及されていない別の解決策:base.pyから継承する複数の設定ファイル(ローカル/ステージング/本番用)がすでにあるため、これは私にとって実装が簡単でした。他の人とは異なり、DATABASESがbase.pyで設定されていないため、DATABASES ['default']を上書きする必要はありませんでした。
SimpleTestCaseは引き続きテストデータベースに接続して移行を実行しようとしました。DATABASESが何も設定されていないconfig / settings / test.pyファイルを作成すると、ユニットテストはそれなしで実行されました。外部キーと一意の制約フィールドを持つモデルを使用することができました。(DBルックアップが必要な外部キーの逆ルックアップは失敗します。)
(Django 2.0.6)
PSコードスニペット
PROJECT_ROOT_DIR/config/settings/test.py:
from .base import *
#other test settings
#DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': 'PROJECT_ROOT_DIR/db.sqlite3',
# }
#}
cli, run from PROJECT_ROOT_DIR:
./manage.py test path.to.app.test --settings config.settings.test
path/to/app/test.py:
from django.test import SimpleTestCase
from .models import *
#^assume models.py imports User and defines Classified and UpgradePrice
class TestCaseWorkingTest(SimpleTestCase):
def test_case_working(self):
self.assertTrue(True)
def test_models_ok(self):
obj = UpgradePrice(title='test',price=1.00)
self.assertEqual(obj.title,'test')
def test_more_complex_model(self):
user = User(username='testuser',email='hi@hey.com')
self.assertEqual(user.username,'testuser')
def test_foreign_key(self):
user = User(username='testuser',email='hi@hey.com')
ad = Classified(user=user,headline='headline',body='body')
self.assertEqual(ad.user.username,'testuser')
#fails with error:
def test_reverse_foreign_key(self):
user = User(username='testuser',email='hi@hey.com')
ad = Classified(user=user,headline='headline',body='body')
print(user.classified_set.first())
self.assertTrue(True) #throws exception and never gets here
ノーズテストランナー(django-nose)を使用すると、次のようなことができます。
my_project/lib/nodb_test_runner.py
:
from django_nose import NoseTestSuiteRunner
class NoDbTestRunner(NoseTestSuiteRunner):
"""
A test runner to test without database creation/deletion
Used for integration tests
"""
def setup_databases(self, **kwargs):
pass
def teardown_databases(self, old_config, **kwargs):
pass
あなたのsettings.py
中で、そこにテストランナーを指定することができます、すなわち
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
または
特定のテストのみを実行したいので、次のように実行します。
python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner