db없이 장고 단위 테스트


126

DB를 설정하지 않고 장고 단위 테스트를 작성할 가능성이 있습니까? db를 설정하지 않아도되는 비즈니스 로직을 테스트하고 싶습니다. 그리고 db를 설정하는 것이 빠르지 만 실제로는 상황에 따라 필요하지 않습니다.


그것이 실제로 중요한지 궁금합니다. 모델이없는 경우 db는 메모리 +에 보관됩니다 .db로 아무것도 수행되지 않습니다. 따라서 필요하지 않은 경우 모델을 설정하지 마십시오.
Torsten Engelbrecht

3
나는 모델이 있지만 그 테스트에는 관련이 없습니다. 그리고 db는 메모리에 보관되지 않지만 mysql에는 특히이 목적으로 빌드됩니다. 나는 이것을 원하지 않는다. 테스트 용으로 메모리 내 db를 사용하도록 django를 구성 할 수있을 것이다. 이 작업을 수행하는 방법을 알고 있습니까?
paweloque

아 죄송합니다. 인 메모리 데이터베이스는 SQLite 데이터베이스를 사용하는 경우입니다. 이것을 제외하고는 테스트 DB 생성을 피할 수있는 방법이 없습니다. 문서에는 이것에 대해 아무것도 없습니다 + 나는 그것을 피할 필요가 없다고 생각했습니다.
Torsten Engelbrecht

3
수락 된 답변이 효과가 없었습니다. 대신이 완벽하게 작동 : caktusgroup.com/blog/2013/10/02/skipping-test-db-creation
휴고 피네다

답변:


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

5
테스트 클래스에 대해 TestCase 대신 SimpleTestCase를 확장해야합니다. TestCase는 데이터베이스가 필요합니다.
Ben Roberts

9
새 설정 파일을 사용하지 않으려면 명령 줄에서 --testrunner옵션을 사용하여 새 TestRunner를 지정할 수 있습니다 .
밀기울 Handley

26
좋은 대답 !! django 1.8에서는 django.test.simple 가져 오기에서 DjangoTestSuiteRunner가 django.test.runner에서 변경되었습니다. Discover Discoverner Hope 누군가에게 도움이됩니다!
조쉬 브라운

2
Django 1.8 이상에서는 위 코드를 약간 수정할 수 있습니다. import 문은 다음과 같이 변경할 수 있습니다. from django.test.runner import DiscoverRunner NoDbTestRunner는 이제 DiscoverRunner 클래스를 확장해야합니다.
Aditya Satyavada

77

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

  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

6
선택한 답변보다 훨씬 간단합니다. 정말 고맙습니다!
KFunk

1
@monkut 아니요 ... SimpleTestCase 클래스 만있는 경우 테스트 러너는 아무것도 실행하지 않습니다 . 이 프로젝트를 참조하십시오 .
Claudio Santos

Django는 SimpleTestCase 만 사용하더라도 테스트 DB를 작성하려고 시도합니다. 이 질문을 참조하십시오 .
Marko Prcać

SimpleTestCase 사용은 유틸리티 메소드 또는 스 니펫 테스트에 정확하게 작동하며 테스트 db를 사용하거나 작성하지 않습니다. 정확히 내가 필요한 것!
Tyro Hunter

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 설정이 필요한지 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 의존 테스트 만 실행할 때 테스트 스위트가 훨씬 빠르게 실행됩니다! :)


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

확인!

이제 다음을 통해 단위 테스트를 엄청나게 빠르고 안전하게 실행할 수 있습니다.

./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

2

또 다른 해결책은 테스트 클래스 unittest.TestCase가 Django의 테스트 클래스 대신 상속되는 것입니다. 장고 문서 ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests )에는 다음에 대한 경고가 포함되어 있습니다.

unittest.TestCase를 사용하면 트랜잭션에서 각 테스트를 실행하고 데이터베이스를 플러시하는 비용을 피할 수 있지만 테스트가 데이터베이스와 상호 작용하는 경우 테스트 실행기가 실행하는 순서에 따라 동작이 달라집니다. 이로 인해 단독으로 실행될 때 통과하지만 스위트에서 실행될 때 실패하는 단위 테스트가 발생할 수 있습니다.

그러나 테스트에서 데이터베이스를 사용하지 않는 경우이 경고는 걱정할 필요가 없으며 트랜잭션에서 각 테스트 사례를 실행할 필요가 없다는 이점을 얻을 수 있습니다.


이것이 여전히 DB를 생성하고 파괴하는 것처럼 보이지만 유일한 차이점은 트랜잭션에서 테스트를 실행하지 않고 DB를 플러시하지 않는다는 것입니다.
캠 레일

0

위의 솔루션도 좋습니다. 그러나 다음 솔루션은 마이그레이션 수가 더 많은 경우에도 DB 작성 시간을 줄입니다. 단위 테스트 중에 남쪽 마이그레이션을 모두 실행하는 대신 syncdb를 실행하면 속도가 훨씬 빨라집니다.

SOUTH_TESTS_MIGRATE = False # 마이그레이션을 비활성화하고 대신 syncdb를 사용하려면


0

내 웹 호스트는 웹 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 조회가 필요한 역방향 외래 키 조회가 실패합니다.)

(장고 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

코 테스트 러너 (장고 코)를 사용할 때 다음과 같이 할 수 있습니다.

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.