web-dev-qa-db-ja.com

django dbなしの単体テスト

Dbを設定せずにDjango unittestsを書く可能性はありますか?dbを設定する必要のないビジネスロジックをテストしたいと思います。状況によっては本当に必要ありません。

106
paweloque

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'

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

108
mohi666

一般に、アプリケーションのテストは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)

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

57
Ali

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
28
themadmax

私は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

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'

これで、非データベース依存テストのみを実行すると、テストスイートの実行速度が1桁速くなります。 :)

8
Paul

更新:サードパーティツール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
6
Rockallite

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
2
Tecuya

別の解決策は、テストクラスにunittest.TestCase Djangoのテストクラスの代わりに。 Django docs( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests )には、次の警告が含まれていますこの:

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

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

2
Kurt Peek

ノーズテストランナー(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
0
radtek

言及されていない別のソリューション: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
0
Simone

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

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

0
venkat

私の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
Chirael