Django: лучший способ юнит-тестирования абстрактной модели - PullRequest
21 голосов
/ 26 ноября 2010

Мне нужно написать несколько модульных тестов для абстрактной базовой модели, которая предоставляет некоторые базовые функциональные возможности, которые должны использоваться другими приложениями.Было бы необходимо определить модель, которая наследуется от нее только для целей тестирования;Есть ли элегантные / простые способы определить эту модель только для тестирования ?

Я видел несколько «хаков», которые делают это возможным, но никогда не видел «официального» способа в документации django или в других подобных местах.

Ответы [ 10 ]

17 голосов
/ 01 декабря 2010

Просто наткнулся на эту функцию сам: вы можете просто наследовать свою абстрактную модель в tests.py и протестировать ее как обычно. Когда вы запускаете «manage.py tests», Django не только создает тестовую базу данных, но также проверяет и синхронизирует ваши тестовые модели.

Протестировано с текущей магистралью Django (версия 1.2).

10 голосов
/ 21 июля 2017

У меня тоже такая же ситуация. Я закончил тем, что использовал версию решения @dylanboxalot. Получил дополнительную информацию от здесь , в частности, после прочтения раздела «Обзор структуры теста».

Методы setUp и tearDown вызываются при каждом запуске теста. Лучшее решение состоит в том, чтобы запустить создание «абстрактной» модели один раз, прежде чем будут выполнены все тесты. Для этого вы можете реализовать setUpClassData, а также реализовать tearDownClass.

class ModelMixinTestCase(TestCase):
    '''
    Base class for tests of model mixins. To use, subclass and specify the
    mixin class variable. A model using the mixin will be made available in
    self.model
    '''
    @classmethod
    def setUpClass(cls):
        # Create a dummy model which extends the mixin
        cls.model = ModelBase('__TestModel__' +
            cls.mixin.__name__, (cls.mixin,),
            {'__module__': cls.mixin.__module__}
        )

        # Create the schema for  our test model
        with connection.schema_editor() as schema_editor:
            schema_editor.create_model(cls.model)
        super(ModelMixinTestCase, cls).setUpClass()

    @classmethod
    def tearDownClass(cls):
        # Delete the schema for the test model
        with connection.schema_editor() as schema_editor:
            schema_editor.delete_model(cls.model)
        super(ModelMixinTestCase, cls).tearDownClass()

Возможная реализация может выглядеть так:

class MyModelTestCase(ModelMixinTestCase):
    mixin = MyModel

    def setUp(self):
        # Runs every time a test is run.
        self.model.objects.create(pk=1)

    def test_my_unit(self):
        # a test
        aModel = self.objects.get(pk=1)
        ...

Может быть, класс ModelMixinTestCase должен быть добавлен в Django? : P

8 голосов
/ 12 марта 2017

Я недавно наткнулся на это и хотел обновить его для более новых версий Django (1.9 и выше). Вы можете использовать create_model SchemaEditor вместо устаревшего sql_create_model

from django.db import connection
from django.db.models.base import ModelBase
from django.test import TestCase


class ModelMixinTestCase(TestCase):
    """
    Base class for tests of model mixins. To use, subclass and specify
    the mixin class variable. A model using the mixin will be made
    available in self.model.
    """

    def setUp(self):
        # Create a dummy model which extends the mixin
        self.model = ModelBase('__TestModel__' + self.mixin.__name__, (self.mixin,), {'__module__': self.mixin.__module__})

        # Create the schema for our test model
        with connection.schema_editor() as schema_editor:
            schema_editor.create_model(self.model)

    def tearDown(self):
        # Delete the schema for the test model
        with connection.schema_editor() as schema_editor:
            schema_editor.delete_model(self.model)
7 голосов
/ 03 июля 2018

Обновлено для Django> = 2.0

Поэтому я столкнулся с несколькими проблемами, используя ответ m4rk4l: одна из них - проблема «RuntimeWarning: Model» myapp .__ test__mymodel 'уже была зарегистрирована'В одном из комментариев упоминается, что тесты не пройдены, поскольку таблица уже существует.

Я добавил несколько проверок, чтобы помочь решить эти проблемы, и теперь он работает безупречно.Я надеюсь, что это поможет людям

from django.db import connection
from django.db.models.base import ModelBase
from django.db.utils import OperationalError
from django.test import TestCase


class AbstractModelMixinTestCase(TestCase):
    """
    Base class for tests of model mixins/abstract models.
    To use, subclass and specify the mixin class variable.
    A model using the mixin will be made available in self.model
    """

@classmethod
def setUpTestData(cls):
    # Create a dummy model which extends the mixin. A RuntimeWarning will
    # occur if the model is registered twice
    if not hasattr(cls, 'model'):
        cls.model = ModelBase(
            '__TestModel__' +
            cls.mixin.__name__, (cls.mixin,),
            {'__module__': cls.mixin.__module__}
        )

    # Create the schema for our test model. If the table already exists,
    # will pass
    try:
        with connection.schema_editor() as schema_editor:
            schema_editor.create_model(cls.model)
        super(AbstractModelMixinTestCase, cls).setUpClass()
    except OperationalError:
        pass

@classmethod
def tearDownClass(self):
    # Delete the schema for the test model. If no table, will pass
    try:
        with connection.schema_editor() as schema_editor:
            schema_editor.delete_model(self.model)
        super(AbstractModelMixinTestCase, self).tearDownClass()
    except OperationalError:
        pass

. Использовать, реализовать так же, как описано выше (теперь с корректирующим отступом):

class MyModelTestCase(AbstractModelMixinTestCase):
    """Test abstract model."""
    mixin = MyModel

    def setUp(self):
        self.model.objects.create(pk=1)

    def test_a_thing(self):
        mod = self.model.objects.get(pk=1)
7 голосов
/ 13 марта 2012

Я думаю, что вы ищете что-то вроде .

Полный код по ссылке:

from django.test import TestCase
from django.db import connection
from django.core.management.color import no_style
from django.db.models.base import ModelBase

class ModelMixinTestCase(TestCase):                                         
    """                                                                     
    Base class for tests of model mixins. To use, subclass and specify      
    the mixin class variable. A model using the mixin will be made          
    available in self.model.                                                
    """                                                                     

    def setUp(self):                                                        
        # Create a dummy model which extends the mixin                      
        self.model = ModelBase('__TestModel__'+self.mixin.__name__, (self.mixin,),
            {'__module__': self.mixin.__module__})                          

        # Create the schema for our test model                              
        self._style = no_style()                                            
        sql, _ = connection.creation.sql_create_model(self.model, self._style)

        self._cursor = connection.cursor()                                  
        for statement in sql:                                               
            self._cursor.execute(statement)                                 

    def tearDown(self):                                                     
        # Delete the schema for the test model                              
        sql = connection.creation.sql_destroy_model(self.model, (), self._style)
        for statement in sql:                                               
            self._cursor.execute(statement)                                 
2 голосов
/ 26 ноября 2010

Разработайте минимальный пример приложения, которое вы распространяете вместе со своими «абстрактными» моделями.Предоставьте тесты для примера приложения, чтобы доказать абстрактные модели.

1 голос
/ 28 ноября 2013

Я сам пришел к этой проблеме, и мое решение на этом суть django-test-abstract-models

вы можете использовать это так:

1- создайте подкласс для ваших абстрактных моделей django

2 - напишите свой тестовый пример следующим образом:

class MyTestCase(AbstractModelTestCase):
    self.models = [MyAbstractModelSubClass, .....]
    # your tests goes here ...

3 - если вы не указали атрибут self.models, он будет искать в текущем приложении моделив пути myapp.tests.models.*

0 голосов
/ 09 июля 2019

В Django 2.2 , если у вас есть только один абстрактный класс для тестирования, вы можете использовать следующее:

from django.db import connection
from django.db import models
from django.db.models.base import ModelBase
from django.db.utils import ProgrammingError
from django.test import TestCase

from yourapp.models import Base  # Base here is the abstract model.


class BaseModelTest(TestCase):
    @classmethod
    def setUpClass(cls):
        # Create dummy model extending Base, a mixin, if we haven't already.
        if not hasattr(cls, '_base_model'):
            cls._base_model = ModelBase(
                'Base',
                ( Base, ),
                { '__module__': Base.__module__ }
            )

            # Create the schema for our base model. If a schema is already
            # create then let's not create another one.
            try:
                with connection.schema_editor() as schema_editor:
                    schema_editor.create_model(cls._base_model)
                super(BaseModelTest, cls).setUpClass()
            except ProgrammingError:
                # NOTE: We get a ProgrammingError since that is what
                #       is being thrown by Postgres. If we were using
                #       MySQL, then we should catch OperationalError
                #       exceptions.
                pass

            cls._test_base = cls._base_model.objects.create()

    @classmethod
    def tearDownClass(cls):
        try:
            with connection.schema_editor() as schema_editor:
                schema_editor.delete_model(cls._base_model)
            super(BaseModelTest, cls).tearDownClass()
        except ProgrammingError:
            # NOTE: We get a ProgrammingError since that is what
            #       is being thrown by Postgres. If we were using
            #       MySQL, then we should catch OperationalError
            #       exceptions.
            pass

Этот ответ - всего лишь настройка Ответ DSynergy . Одно заметное отличие состоит в том, что мы используем setUpClass() вместо setUpTestData(). Это различие важно, поскольку использование последнего приведет к InterfaceError (при использовании PostgreSQL) или эквиваленту в других базах данных при выполнении других тестовых случаев. Что касается причины, по которой это происходит, я не знаю на момент написания.

ПРИМЕЧАНИЕ: Если у вас есть несколько абстрактных классов для тестирования, лучше использовать другие решения.

0 голосов
/ 24 февраля 2019

Я подумал, что могу поделиться с вами своим решением, которое, на мой взгляд, намного проще, и я не вижу никаких минусов.

В примере используется два абстрактных класса.

from django.db import connection
from django.db.models.base import ModelBase
from mailalert.models import Mailalert_Mixin, MailalertManager_Mixin

class ModelMixinTestCase(TestCase):   

    @classmethod
    def setUpTestData(cls):

        # we define our models "on the fly", based on our mixins
        class Mailalert(Mailalert_Mixin):
            """ For tests purposes only, we fake a Mailalert model """
            pass

        class Profile(MailalertManager_Mixin):
            """ For tests purposes only, we fake a Profile model """
            user = models.OneToOneField(User, on_delete=models.CASCADE, 
                related_name='profile', default=None)

        # then we make those models accessible for later
        cls.Mailalert = Mailalert
        cls.Profile = Profile

        # we create our models "on the fly" in our test db
        with connection.schema_editor() as editor:
            editor.create_model(Profile)
            editor.create_model(Mailalert)

        # now we can create data using our new added models "on the fly"
        cls.user = User.objects.create_user(username='Rick')
        cls.profile_instance = Profile(user=cls.user)
        cls.profile_instance.save()
        cls.mailalert_instance = Mailalert()
        cls.mailalert_instance.save()

# then you can use this ModelMixinTestCase
class Mailalert_TestCase(ModelMixinTestCase):
    def test_method1(self):
       self.assertTrue(self.mailalert_instance.method1())
       # etc
0 голосов
/ 28 ноября 2010

Тестирование абстрактного класса не слишком полезно, поскольку производный класс может переопределять его методы.Другие приложения отвечают за тестирование своих классов на основе вашего абстрактного класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...