developer tip

db없이 장고 단위 테스트

optionbox 2020. 8. 5. 08:09
반응형

db없이 장고 단위 테스트


DB를 설정하지 않고 장고 단위 테스트를 작성할 가능성이 있습니까? db를 설정하지 않아도되는 비즈니스 로직을 테스트하고 싶습니다. 그리고 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'

자세한 내용 은 사용자 정의 테스트 러너에 대한 공식 문서 섹션을 확인하십시오 .


일반적으로 응용 프로그램의 테스트는 두 가지 범주로 분류 할 수 있습니다

  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)

이 전략은 데이터베이스에 액세스하는 테스트 케이스에 대해서만 데이터베이스를 작성하고 파괴하므로 테스트가 더 효율적입니다.


에서 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 설정이 필요한지 setup_databases확인하고 DB가 필요한 경우 정상적인 기능을 시작하도록 허용합니다 . 두 번째 추가는 메소드 실행이 허용 된 teardown_databases경우 정규 실행 setup_databases을 허용합니다.

내 코드는 상속 된 TestCase django.test.TransactionTestCase(및 따라서 django.test.TestCase)에서 데이터베이스를 설정해야 한다고 가정합니다 . 장고 문서는 다음과 같이 말했기 때문에이 가정을했습니다.

... 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 의존 테스트 만 실행할 때 테스트 스위트가 훨씬 빠르게 실행됩니다! :)


업데이트 됨 : 타사 도구 사용에 대한 이 답변 도 참조하십시오 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

If you're using South, also run the following command:

./manage.py migrate --settings=mysite.no_db_settings

OK!

You can now run unit tests blazingly fast (and safe) by:

./manage.py test myapp --settings=mysite.no_db_settings

As an alternative to modifying your settings to make NoDbTestRunner "safe", here's a modified version of NoDbTestRunner that closes the current database connection and removes the connection information from settings and the connection object. Works for me, test it in your environment before relying on it :)

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

Another solution would be to have your test class simply inherit from unittest.TestCase instead of any of Django's test classes. The Django docs (https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests) contain the following warning about this:

Using unittest.TestCase avoids the cost of running each test in a transaction and flushing the database, but if your tests interact with the database their behavior will vary based on the order that the test runner executes them. This can lead to unit tests that pass when run in isolation but fail when run in a suite.

However, if your test doesn't use the database, this warning needn't concern you and you can reap the benefits of not having to run each test case in a transaction.


The above solutions are fine too. But the following solution will also reduce the db creation time if there are more number of migrations. During unit testing, running syncdb instead of running all the south migrations will be much faster.

SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb instead


My web host only allows creating and dropping databases from their Web GUI, so I was getting a "Got an error creating the test database: Permission denied" error when trying to run python manage.py test.

I'd hoped to use the --keepdb option to django-admin.py but it doesn't seem to be supported any longer as of Django 1.7.

What I ended up doing was modifying the Django code in .../django/db/backends/creation.py, specifically the _create_test_db and _destroy_test_db functions.

For _create_test_db I commented out the cursor.execute("CREATE DATABASE ... line and replaced it with pass so the try block wouldn't be empty.

For _destroy_test_db I just commented out cursor.execute("DROP DATABASE - I didn't need to replace it with anything because there was already another command in the block (time.sleep(1)).

After that my tests ran fine - though I did set up a test_ version of my regular database separately.

This isn't a great solution of course, because it will break if Django is upgraded, but I had a local copy of Django due to using virtualenv so at least I have control over when/if I upgrade to a newer version.


Another solution not mentioned: this was easy for me to implement because I already have multiple settings files (for local / staging / production) that inherit from base.py . So unlike other people I did not have to overwrite DATABASES['default'], as DATABASES isn't set in base.py

SimpleTestCase still tried to connect to my test database and run migrations. When I made a config/settings/test.py file that didn't set DATABASES to anything, then my unit tests ran without it. It allowed me to use models that had foreign key and unique constraint fields. (Reverse foreign key lookup, which requires a db lookup, fails.)

(Django 2.0.6)

PS code snippets

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

When using the nose test runner (django-nose), you can do something like this:

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

In your settings.py you can specify the test runner there, i.e.

TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'

OR

I wanted it for running specific tests only, so I run it like so:

python manage.py test integration_tests/integration_*  --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner

참고URL : https://stackoverflow.com/questions/5917587/django-unit-tests-without-a-db

반응형