DBなしのdjango単体テスト


126

データベースを設定せずにdjangoユニットテストを書く可能性はありますか?データベースの設定を必要としないビジネスロジックをテストしたい。また、dbのセットアップは高速ですが、状況によっては必要ない場合もあります。


それが本当に重要かどうか私は思っています。+ dbはメモリに保持されます。モデルがない場合、dbでは何も実行されません。したがって、必要がない場合は、モデルを設定しないでください。
Torsten Engelbrecht、

3
私にはモデルがありますが、それらのテストには関係ありません。そして、dbはメモリに保持されませんが、特にこの目的のためにmysqlで構築されます。私がこれを望んでいるわけではありません。多分私はテストのためにメモリ内データベースを使用するようにdjangoを設定することができます。これを行う方法を知っていますか?
11

あ、ごめんなさい。インメモリデータベースは、SQLiteデータベースを使用する場合にのみ当てはまります。これ以外は、テストデータベースの作成を回避する方法はわかりません。ドキュメントにはこれについては何もありません+私はそれを避ける必要性を感じたことはありません。
Torsten Engelbrecht、

3
受け入れられた回答はうまくいきませんでした。代わりに、これは完全に機能しました:caktusgroup.com/blog/2013/10/02/skipping-test-db-creation
Hugo Pineda

回答:


122

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'

詳細については、カスタムテストランナーに関する公式ドキュメントセクションをご覧ください。


2
このエラーは、データベーストランザクションを必要とするテストがある場合に発生します。DBがない場合は、これらのテストを実行できないことは明らかです。テストは個別に実行する必要があります。python manage.py test --settings = new_settings.pyを使用してテストを実行するだけの場合、データベースを必要とする可能性のある他のアプリから他の一連のテストを実行します。
mohi666 2011

5
テストクラスのTestCaseではなくSimpleTestCaseを拡張する必要があることに注意してください。TestCaseはデータベースを想定しています。
ベンロバーツ

9
新しい設定ファイルを使用したくない場合は、--testrunnerオプションを使用してコマンドラインで新しいTestRunnerを指定できます。
ふすま扱い

26
正解です!django 1.8では、django.test.simpleインポートからDjangoTestSuiteRunnerがdjango.test.runner importからDiscoverRunner Hopeに変更され、誰かを助けます!
Josh Brown

2
Django 1.8以降では、上記のコードにわずかな修正を加えることができます。importステートメントは次のように変更できます。django.test.runner import DiscoverRunnerからNoDbTestRunnerはDiscoverRunnerクラスを拡張する必要があります。
Aditya Satyavada 2018年

77

一般に、アプリケーションのテストは2つのカテゴリに分類できます

  1. 単体テスト、これらは日射のコードの個々のスニペットをテストし、データベースに行く必要はありません
  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)

この戦略により、データベースにアクセスするテストケースに対してのみデータベースが作成および破棄されるため、テストがより効率的になります。


37
これにより、テストの実行がより効率的になりますが、テストランナーは初期化時にテストデータベースを作成することに注意してください。
monkut 2014年

6
非常に単純なので、選択した答え。どうもありがとうございます!
KFunk 2016年

1
@monkutいいえ... SimpleTestCaseクラスしかない場合、テストランナーは何も実行しません。このプロジェクトを参照してください。
Claudio Santos

SimpleTestCaseのみを使用している場合でも、Djangoは引き続きテストDBを作成しようとします。この質問を参照してください。
MarkoPrcać19年

SimpleTestCaseの使用は、ユーティリティメソッドまたはスニペットのテストに正確に機能し、テストデータベースを使用または作成しません。まさに私が必要なもの!
タイロハンター

28

から 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

8

私はメソッドから継承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

mysite / scripts / settings.py

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ファイルに追加しました。

mysite / settings.py

TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'

ここで、dbに依存しないテストのみを実行すると、私のテストスイートは桁違いに速く実行されます!:)


6

更新:サードパーティツールの使用については、この回答もご覧ください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

私はpytest(pytest-djangoプラグインを使用)とNoDbTestRunnerを使用してテストを実行しましたが、何らかの理由でテストケースで偶然にオブジェクトを作成し、データベース名を上書きしなかった場合、オブジェクトはローカルデータベースに作成されます。設定。「NoDbTestRunner」という名前は「NoTestDbTestRunner」である必要があります。テストデータベースは作成されませんが、設定からデータベースを使用するためです。
Gabriel Muj

2

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

注:接続リストからデフォルトの接続を削除すると、Djangoモデルや、通常はデータベースを使用するその他の機能を使用できなくなります(明らかに、データベースとは通信しませんが、DjangoはDBがサポートするさまざまな機能をチェックします)。 。また、connections._connectionsはサポートされ__getitem__なくなったようです。使用connections._connections.defaultオブジェクトにアクセスするためです。
the_drow 2013

2

別の解決策はunittest.TestCase、Djangoのテストクラスの代わりにテストクラスを単に継承させることです。Djangoのドキュメント(https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests)には、これに関する次の警告が含まれています。

unittest.TestCaseを使用すると、トランザクションで各テストを実行してデータベースをフラッシュするコストを回避できますが、テストがデータベースと対話する場合、その動作はテストランナーが実行する順序によって異なります。これは、単体テストで実行すると合格し、スイートで実行すると失敗する単体テストにつながる可能性があります。

ただし、テストでデータベースを使用しない場合、この警告は関係ありません。トランザクションで各テストケースを実行する必要がないという利点を享受できます。


これはまだdbを作成して破棄するように見えますが、唯一の違いは、トランザクションでテストを実行せず、dbをフラッシュしないことです。
カムレール

0

上記のソリューションも問題ありません。ただし、次の解決策では、移行の数が増えると、データベースの作成時間が短縮されます。単体テスト中、すべての南側の移行を実行する代わりにsyncdbを実行すると、はるかに高速になります。

SOUTH_TESTS_MIGRATE = False#移行を無効にして、代わりにsyncdbを使用するには


0

私の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のローカルコピーがあったため、少なくとも新しいバージョンにアップグレードするかどうかを制御できます。


0

言及されていない別の解決策: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

0

ノーズテストランナー(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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.