Nice programing

Django : 테스트를 위해 동적으로 모델을 만드는 방법

nicepro 2020. 11. 30. 19:54
반응형

Django : 테스트를 위해 동적으로 모델을 만드는 방법


settings다음 형식의 속성 이 필요한 Django 앱이 있습니다 .

RELATED_MODELS = ('appname1.modelname1.attribute1',
                  'appname1.modelname2.attribute2', 
                  'appname2.modelname3.attribute3', ...)

그런 다음 post_save 신호를 연결하여 attributeN정의 된 항목 에 따라 다른 고정 모델을 업데이트합니다 .

이 동작을 테스트하고 싶습니다.이 앱이 프로젝트에서 유일한 앱인 경우에도 테스트가 작동해야합니다 (자체 종속성을 제외하고 다른 래퍼 앱을 설치할 필요가 없음). 테스트 데이터베이스에 대한 모의 모델을 생성하고 연결 / 등록 / 활성화하려면 어떻게해야합니까? (또는 가능합니까?)

테스트 픽스처를 사용할 수있는 솔루션이 좋을 것입니다.


파일이 tests/아닌 앱 하위 디렉터리에 테스트를 배치 하고 테스트 전용 모델에를 포함 할 수 있습니다.tests.pytests/models.py

그런 다음에 '앱' 을 포함 하는 테스트 실행 스크립트 ( )를 제공합니다 . (이는 테스트 앱이없는 실제 프로젝트에서 앱 테스트를 실행할 때는 작동하지 않지만 프로젝트에서 재사용 가능한 앱 테스트를 실행하는 것이 거의 유용하지 않으며 Django 1.6 이상은 기본적으로 그렇지 않습니다. )tests/INSTALLED_APPSINSTALLED_APPS

( 참고 : 아래에 설명 된 대체 동적 방법은 테스트 케이스 하위 클래스 TransactionTestCase가 테스트 속도를 상당히 느리게하고 Django 1.7 이상에서 더 이상 작동하지 않는 경우 Django 1.1 이상에서만 작동합니다 . 역사적 관심사를 위해서만 여기에 남아 있습니다. 그걸 써.)

테스트를 시작할 때 (즉, setUp 메서드에서 또는 doctest 집합의 시작 부분에서) "myapp.tests"INSTALLED_APPS 설정에 동적으로 추가 한 다음 다음을 수행 할 수 있습니다.

from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)

그런 다음 테스트가 끝나면 이전 버전의 INSTALLED_APPS를 복원하고 앱 캐시를 다시 삭제하여 정리해야합니다.

이 클래스 는 패턴을 캡슐화하여 테스트 코드를 복잡하게 만들지 않습니다.


@paluh의 대답은 비 테스트 파일에 원치 않는 코드를 추가해야하며 내 경험상 @carl의 솔루션은 고정 장치를 사용하는 데 필요한 django.test.TestCase에서 작동하지 않습니다. django.test.TestCase를 사용하려면 조명기가로드되기 전에 syncdb를 호출해야합니다. 이를 위해서는 _pre_setup 메서드를 재정의해야합니다 (setUp 메서드에 코드를 입력하는 것만으로는 충분하지 않습니다). 테스트 모델과 함께 앱을 추가 할 수있는 자체 버전의 TestCase를 사용합니다. 다음과 같이 정의됩니다.

from django.conf import settings
from django.core.management import call_command
from django.db.models import loading
from django import test

class TestCase(test.TestCase):
    apps = ()

    def _pre_setup(self):
        # Add the models to the db.
        self._original_installed_apps = list(settings.INSTALLED_APPS)
        for app in self.apps:
            settings.INSTALLED_APPS.append(app)
        loading.cache.loaded = False
        call_command('syncdb', interactive=False, verbosity=0)
        # Call the original method that does the fixtures etc.
        super(TestCase, self)._pre_setup()

    def _post_teardown(self):
        # Call the original method.
        super(TestCase, self)._post_teardown()
        # Restore the settings.
        settings.INSTALLED_APPS = self._original_installed_apps
        loading.cache.loaded = False

프로젝트에서 사용하는 솔루션공유 했습니다. 누군가에게 도움이 될 수도 있습니다.

pip install django-fake-model

가짜 모델을 만드는 두 가지 간단한 단계 :

1) 모든 파일에서 모델을 정의합니다 (보통 테스트 케이스 근처의 테스트 파일에서 모델을 정의합니다).

from django_fake_model import models as f


class MyFakeModel(f.FakeModel):

    name = models.CharField(max_length=100)

2) TestCase 또는 테스트 기능에 데코레이터 @MyFakeModel.fake_me추가하십시오 .

class MyTest(TestCase):

    @MyFakeModel.fake_me
    def test_create_model(self):
        MyFakeModel.objects.create(name='123')
        model = MyFakeModel.objects.get(name='123')
        self.assertEqual(model.name, '123')

이 데코레이터는 각 테스트 전에 데이터베이스에 테이블을 만들고 테스트 후에 테이블을 제거합니다.

또한 테이블을 수동으로 생성 / 삭제할있습니다 . MyFakeModel.create_table()/MyFakeModel.delete_table()


이 솔루션은 django(이전 1.7) 의 이전 버전에서만 작동합니다 . 버전을 쉽게 확인할 수 있습니다.

import django
django.VERSION < (1, 7)

원래 응답 :

매우 이상하지만 Form me는 매우 간단한 패턴으로 작동합니다.

  1. 테스트 할 앱에 tests.py를 추가하고,
  2. 이 파일에서 테스트 모델을 정의하고
  3. 아래에 테스트 코드 (doctest 또는 TestCase 정의)를 입력하십시오.

아래에 테스트에만 필요한 Article 모델을 정의하는 코드를 넣었습니다 (someapp / tests.py에 있으며 ./manage.py test someapp으로 테스트 할 수 있습니다 ).

class Article(models.Model):
    title = models.CharField(max_length=128)
    description = models.TextField()
    document = DocumentTextField(template=lambda i: i.description)

    def __unicode__(self):
        return self.title

__test__ = {"doctest": """
#smuggling model for tests
>>> from .tests import Article

#testing data
>>> by_two = Article.objects.create(title="divisible by two", description="two four six eight")
>>> by_three = Article.objects.create(title="divisible by three", description="three six nine")
>>> by_four = Article.objects.create(title="divisible by four", description="four four eight")

>>> Article.objects.all().search(document='four')
[<Article: divisible by two>, <Article: divisible by four>]
>>> Article.objects.all().search(document='three')
[<Article: divisible by three>]
"""}

단위 테스트는 이러한 모델 정의로도 작동합니다.


테스트를 위해 모델을 동적으로 생성하기 위해 약간 다른 접근 방식을 선택했습니다.

testsfiles에있는 하위 디렉터리 에 모든 테스트를 보관합니다 . 하위 디렉토리 models.py파일 tests에는 테스트 전용 모델이 포함되어 있습니다. 결합 된 부분이 여기에 들어 오면 settings.py파일에 다음을 추가해야 합니다.

# check if we are testing right now
TESTING = 'test' in sys.argv

if TESTING:
    # add test packages that have models
    INSTALLED_APPS += ['files.tests',]

또한 테스트 모델에서 db_table을 설정했습니다. 그렇지 않으면 Django가 tests_<model_name>다른 앱의 다른 테스트 모델과 충돌을 일으킬 수 있는 이름으로 테이블을 만들었 기 때문 입니다. 내 테스트 모델은 다음과 같습니다.

class Recipe(models.Model):

    '''Test-only model to test out thumbnail registration.'''

    dish_image = models.ImageField(upload_to='recipes/')

    class Meta:
        db_table = 'files_tests_recipe'

관련 답변 에서 인용 :

테스트 용으로 만 정의 된 모델을 원한다면 Django 티켓 # 7835 , 특히 아래에 주어진 코멘트 # 24 부분을 확인해야 합니다.

분명히 tests.py에서 직접 모델을 정의 할 수 있습니다. Syncdb는 tests.py를 가져 오지 않으므로 해당 모델은 일반 db에 동기화되지 않지만 테스트 데이터베이스에 동기화되고 테스트에 사용할 수 있습니다.


django 1.7+ 용 테스트 전용 모델을위한 방법을 알아 냈습니다.

기본적인 아이디어는 당신의 확인입니다 tests응용 프로그램을, 그리고 추가 tests에를 INSTALLED_APPS.

예를 들면 다음과 같습니다.

$ ls common
__init__.py   admin.py      apps.py       fixtures      models.py     pagination.py tests         validators.py views.py

$ ls common/tests
__init__.py        apps.py            models.py          serializers.py     test_filter.py     test_pagination.py test_validators.py views.py

그리고 나는 settings다른 목적 (ref : splitting up the settings file ), 즉 :

  • settings/default.py: 기본 설정 파일
  • settings/production.py: 생산 용
  • settings/development.py: 개발 용
  • settings/testing.py: 시험용.

그리고에서에게 settings/testing.py, 당신은 수정할 수 있습니다 INSTALLED_APPS:

settings/testing.py:

from default import *

DEBUG = True

INSTALLED_APPS += ['common', 'common.tests']

테스트 앱에 적절한 라벨을 설정했는지 확인하세요.

common/tests/apps.py

from django.apps import AppConfig


class CommonTestsConfig(AppConfig):
    name = 'common.tests'
    label = 'common_tests'

common/tests/__init__.py, 적절한 설정 AppConfig(참조 : Django Applications ).

default_app_config = 'common.tests.apps.CommonTestsConfig'

그런 다음 다음을 통해 db 마이그레이션을 생성하십시오.

python manage.py makemigrations --settings=<your_project_name>.settings.testing tests

마지막으로 param으로 테스트를 실행할 수 있습니다 --settings=<your_project_name>.settings.testing.

py.test를 사용하면 pytest.inidjango의 manage.py.

py.test

[pytest]
DJANGO_SETTINGS_MODULE=kungfu.settings.testing

여기에 제가 사용하는 패턴이 있습니다.

저는 TestCase의 하위 클래스 버전에서 사용하는이 메서드를 작성했습니다. 다음과 같이 진행됩니다.

@classmethod
def create_models_from_app(cls, app_name):
    """
    Manually create Models (used only for testing) from the specified string app name.
    Models are loaded from the module "<app_name>.models"
    """
    from django.db import connection, DatabaseError
    from django.db.models.loading import load_app

    app = load_app(app_name)
    from django.core.management import sql
    from django.core.management.color import no_style
    sql = sql.sql_create(app, no_style(), connection)
    cursor = connection.cursor()
    for statement in sql:
        try:
            cursor.execute(statement)
        except DatabaseError, excn:
            logger.debug(excn.message)
            pass

그런 다음 myapp/tests/models.pyINSTALLED_APPS에 포함되지 않은 특수한 테스트 전용 models.py 파일을 만듭니다 .

내 setUp 메소드에서 create_models_from_app ( 'myapp.tests')를 호출하고 적절한 테이블을 생성합니다.

The only "gotcha" with this approach is that you don't really want to create the models ever time setUp runs, which is why I catch DatabaseError. I guess the call to this method could go at the top of the test file and that would work a little better.


Combining your answers, specially @slacy's, I did this:

class TestCase(test.TestCase):
    initiated = False

    @classmethod
    def setUpClass(cls, *args, **kwargs):
        if not TestCase.initiated:
            TestCase.create_models_from_app('myapp.tests')
            TestCase.initiated = True

        super(TestCase, cls).setUpClass(*args, **kwargs)

    @classmethod
    def create_models_from_app(cls, app_name):
        """
        Manually create Models (used only for testing) from the specified string app name.
        Models are loaded from the module "<app_name>.models"
        """
        from django.db import connection, DatabaseError
        from django.db.models.loading import load_app

        app = load_app(app_name)
        from django.core.management import sql
        from django.core.management.color import no_style
        sql = sql.sql_create(app, no_style(), connection)
        cursor = connection.cursor()
        for statement in sql:
            try:
                cursor.execute(statement)
            except DatabaseError, excn:
                logger.debug(excn.message)

With this, you don't try to create db tables more than once, and you don't need to change your INSTALLED_APPS.


If you are writing a reusable django-app, create a minimal test-dedicated app for it!

$ django-admin.py startproject test_myapp_project
$ django-admin.py startapp test_myapp

add both myapp and test_myapp to the INSTALLED_APPS, create your models there and it's good to go!

I have gone through all these answers as well as django ticket 7835, and I finally went for a totally different approach. I wanted my app (somehow extending queryset.values() ) to be able to be tested in isolation; also, my package does include some models and I wanted a clean distinction between test models and package ones.

That's when I realized it was easier to add a very small django project in the package! This also allows a much cleaner separation of code IMHO:

In there you can cleanly and without any hack define your models, and you know they will be created when you run your tests from in there!

If you are not writing an independent, reusable app you can still go this way: create a test_myapp app, and add it to your INSTALLED_APPS only in a separate settings_test_myapp.py!


Someone already mentioned Django ticket #7835, but there appears to be a more recent reply that looks much more promising for more recent versions of Django. Specifically #42, which proposes a different TestRunner:

from importlib.util import find_spec
import unittest

from django.apps import apps
from django.conf import settings
from django.test.runner import DiscoverRunner


class TestLoader(unittest.TestLoader):
    """ Loader that reports all successful loads to a runner """
    def __init__(self, *args, runner, **kwargs):
        self.runner = runner
        super().__init__(*args, **kwargs)

    def loadTestsFromModule(self, module, pattern=None):
        suite = super().loadTestsFromModule(module, pattern)
        if suite.countTestCases():
            self.runner.register_test_module(module)
        return suite


class RunnerWithTestModels(DiscoverRunner):
    """ Test Runner that will add any test packages with a 'models' module to INSTALLED_APPS.
        Allows test only models to be defined within any package that contains tests.
        All test models should be set with app_label = 'tests'
    """
    def __init__(self, *args, **kwargs):
        self.test_packages = set()
        self.test_loader = TestLoader(runner=self)
        super().__init__(*args, **kwargs)

    def register_test_module(self, module):
        self.test_packages.add(module.__package__)

    def setup_databases(self, **kwargs):
        # Look for test models
        test_apps = set()
        for package in self.test_packages:
            if find_spec('.models', package):
                test_apps.add(package)
        # Add test apps with models to INSTALLED_APPS that aren't already there
        new_installed = settings.INSTALLED_APPS + tuple(ta for ta in test_apps if ta not in settings.INSTALLED_APPS)
        apps.set_installed_apps(new_installed)
        return super().setup_databases(**kwargs)

참고URL : https://stackoverflow.com/questions/502916/django-how-to-create-a-model-dynamically-just-for-testing

반응형