[django] Django에서 다른 설정으로 단위 테스트하는 방법은 무엇입니까?

단위 테스트를 위해 Django 설정을 재정의하는 간단한 메커니즘이 있습니까? 특정 수의 최신 개체를 반환하는 내 모델 중 하나에 관리자가 있습니다. 반환되는 개체 수는 NUM_LATEST 설정에 의해 정의됩니다.

누군가가 설정을 변경하면 내 테스트가 실패 할 가능성이 있습니다. 설정을 무시 setUp()하고 나중에 복원 하려면 어떻게 해야 tearDown()합니까? 그것이 가능하지 않다면 원숭이 패치 방법이나 설정을 모의 할 수있는 방법이 있습니까?

편집 : 다음은 내 관리자 코드입니다.

class LatestManager(models.Manager):
    """
    Returns a specific number of the most recent public Articles as defined by 
    the NEWS_LATEST_MAX setting.
    """
    def get_query_set(self):
        num_latest = getattr(settings, 'NEWS_NUM_LATEST', 10)
        return super(LatestManager, self).get_query_set().filter(is_public=True)[:num_latest]

관리자는 settings.NEWS_LATEST_MAX쿼리 세트를 분할하는 데 사용 합니다. 는 getattr()단순히 설정이 존재하지 않아야 기본을 제공하는 데 사용됩니다.



답변

편집 :이 답변은 적은 수의 특정 테스트에 대한 설정을 변경하려는 경우 적용됩니다 .

Django 1.4부터 테스트 중에 설정을 재정의하는 방법이 있습니다.
https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCase에는 self.settings 컨텍스트 관리자가 있으며 테스트 메서드 또는 전체 TestCase 하위 클래스에 적용 할 수있는 @override_settings 데코레이터도 있습니다.

이러한 기능은 Django 1.3에 아직 존재하지 않았습니다.

모든 테스트에 대한 설정을 변경하려면 기본 설정 파일에서 설정을로드하고 재정의 할 수있는 별도의 테스트 설정 파일을 만들어야합니다. 다른 답변에는 이에 대한 몇 가지 좋은 접근 방식이 있습니다. 나는 hspanderdmitrii의 접근 방식 모두에서 성공적인 변형을 보았습니다 .


답변

UnitTest인스턴스 속성 설정 및 읽기를 포함 하여 하위 클래스에 대해 원하는 모든 작업을 수행 할 수 있습니다 .

from django.conf import settings

class MyTest(unittest.TestCase):
   def setUp(self):
       self.old_setting = settings.NUM_LATEST
       settings.NUM_LATEST = 5 # value tested against in the TestCase

   def tearDown(self):
       settings.NUM_LATEST = self.old_setting

그러나 django 테스트 케이스는 단일 스레드로 실행되기 때문에 NUM_LATEST 값을 수정하는 다른 것이 무엇인지 궁금합니다. 만약 그 “다른 것”이 당신의 테스트 루틴에 의해 촉발된다면, 나는 어떤 양의 원숭이 패치가 테스트 자체의 진실성을 무효화하지 않고 테스트를 저장할 것이라고 확신하지 못합니다.


답변

런타임에서 설정 구성을 재정의하는 것이 도움이 될 수 있지만 제 생각에는 테스트를 위해 별도의 파일을 만들어야합니다. 이렇게하면 테스트를위한 많은 구성이 절약되며, 이는 복구 할 수없는 작업 (예 : 스테이징 데이터베이스 정리)을 수행하지 않도록합니다.

테스트 파일이 ‘my_project / test_settings.py’에 있다고 가정하고

settings = 'my_project.test_settings' if 'test' in sys.argv else 'my_project.settings'

manage.py에서. 이렇게하면 실행할 때 python manage.py testtest_settings 만 사용할 수 있습니다. pytest와 같은 다른 테스트 클라이언트를 사용하는 경우 쉽게 pytest.ini에 추가 할 수 있습니다.


답변

--settings테스트를 실행할 때 옵션 을 전달할 수 있습니다.

python manage.py test --settings=mysite.settings_local


답변

업데이트 : 아래 솔루션은 Django 1.3.x 및 이전 버전에서만 필요합니다. > 1.4의 경우 slinkp의 답변을 참조하십시오 .

테스트에서 설정을 자주 변경하고 Python ≥2.5를 사용하는 경우에도이 방법이 편리합니다.

from contextlib import contextmanager

class SettingDoesNotExist:
    pass

@contextmanager
def patch_settings(**kwargs):
    from django.conf import settings
    old_settings = []
    for key, new_value in kwargs.items():
        old_value = getattr(settings, key, SettingDoesNotExist)
        old_settings.append((key, old_value))
        setattr(settings, key, new_value)
    yield
    for key, old_value in old_settings:
        if old_value is SettingDoesNotExist:
            delattr(settings, key)
        else:
            setattr(settings, key, old_value)

그런 다음 다음을 수행 할 수 있습니다.

with patch_settings(MY_SETTING='my value', OTHER_SETTING='other value'):
    do_my_tests()


답변

@override_settings 프로덕션 환경과 테스트 환경 구성 사이에 차이가 많지 않은 경우 유용합니다.

다른 경우에는 다른 설정 파일을 사용하는 것이 좋습니다. 이 경우 프로젝트는 다음과 같습니다.

your_project
    your_app
        ...
    settings
        __init__.py
        base.py
        dev.py
        test.py
        production.py
    manage.py

따라서 대부분의 설정 base.py이 있어야하며 다른 파일에서 모든 것을 가져 와서 일부 옵션을 재정의해야합니다. 여기 무슨 test.py파일이 다음과 같이 표시됩니다

from .base import *

DEBUG = False

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'app_db_test'
    }
}

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

그런 다음 --settings@MicroPyramid 응답에서와 같이 옵션 을 지정 하거나 DJANGO_SETTINGS_MODULE환경 변수를 지정한 다음 테스트를 실행할 수 있습니다.

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 


답변

일부 doctest를 수정하는 동안이 문제를 발견했습니다 … 완전성을 위해 doctest를 사용할 때 설정을 수정하려면 다른 항목을 가져 오기 전에 변경해야한다는 점을 말씀 드리고 싶습니다.

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from django.core.paginator import Paginator
>>> # etc