Dbを設定せずにDjango unittestsを書く可能性はありますか?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
移動された to'Django.test.runner.DiscoverRunner'
。
詳細については、 公式ドキュメント カスタムテストランナーに関するセクションをご覧ください。
一般に、アプリケーションのテストは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 say:
...などのより複雑で重量のあるDjango固有の機能が必要な場合は、ORMのテストまたは使用...代わりに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'
これで、非データベース依存テストのみを実行すると、テストスイートの実行速度が1桁速くなります。 :)
更新:サードパーティツール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
別の解決策は、テストクラスにunittest.TestCase
Djangoのテストクラスの代わりに。 Django docs( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests )には、次の警告が含まれていますこの:
Unittest.TestCaseを使用すると、トランザクションで各テストを実行してデータベースをフラッシュするコストを回避できますが、テストがデータベースと対話する場合、テストランナーが実行する順序に基づいて動作が異なります。これにより、単体で実行されるとパスするが、スイートで実行されると失敗する単体テストにつながる可能性があります。
ただし、テストでデータベースを使用しない場合、この警告は気にする必要がなく、トランザクションで各テストケースを実行する必要がないという利点を享受できます。
ノーズテストランナー(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'
[〜#〜] or [〜#〜]
特定のテストのみを実行するために必要だったため、次のように実行します。
python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner
言及されていない別のソリューション:base.pyを継承する複数の設定ファイル(ローカル/ステージング/プロダクション用)が既にあるため、これは実装が簡単でした。だから、他の人々とは異なり、DATABASESはbase.pyで設定されていないため、DATABASES ['default']を上書きする必要はありませんでした。
SimpleTestCaseはまだテストデータベースに接続して移行を実行しようとしました。 DATABASESを何も設定しないconfig/settings/test.pyファイルを作成したとき、ユニットテストはそれなしで実行されました。外部キーと一意の制約フィールドを持つモデルを使用できました。 (データベース検索を必要とする逆外部キー検索は失敗します。)
(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='[email protected]')
self.assertEqual(user.username,'testuser')
def test_foreign_key(self):
user = User(username='testuser',email='[email protected]')
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='[email protected]')
ad = Classified(user=user,headline='headline',body='body')
print(user.classified_set.first())
self.assertTrue(True) #throws exception and never gets here
上記のソリューションも問題ありません。ただし、移行の数が多い場合は、次のソリューションでもデータベースの作成時間が短縮されます。単体テスト中に、すべての南の移行を実行する代わりにsyncdbを実行すると、はるかに高速になります。
SOUTH_TESTS_MIGRATE = False#移行を無効にし、代わりにsyncdbを使用するには
私の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のローカルコピーがありました。少なくとも、いつ新しいバージョンにアップグレードするかを制御できます。