Django: лучший способ модульного тестирования абстрактной модели
Мне нужно написать некоторые модульные тесты для абстрактной базовой модели, которая предоставляет некоторые базовые функции, которые должны использоваться другими приложениями. Было бы необходимо определить модель, которая наследуется от нее только для целей тестирования; существуют ли какие-либо элегантные/простые способы определения этой модели только для тестирования?
Я видел некоторые "хаки", которые делают это возможным, но никогда не видели "официального" способа в документации django или в других подобных местах.
Ответы
Ответ 1
Просто наткнулся на эту функцию сам: вы можете просто унаследовать свою абстрактную модель в tests.py и протестировать ее как обычно. Когда вы запускаете "manage.py tests", Django не только создает тестовую базу данных, но также проверяет и синхронизирует ваши тестовые модели.
Протестировал его с текущей магистралью Django (версия 1.2).
Ответ 2
У меня такая же ситуация. Я закончил тем, что использовал версию решения @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? :П
Ответ 3
Я наткнулся на это недавно и хотел обновить его для новых версий Django (1.9 и более поздних версий). Вместо устаревшего sql_create_model
вы можете использовать SchemaEditor 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)
Ответ 4
Я думаю, что вы ищете что-то вроде этого.
Это полный код по ссылке:
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)
Ответ 5
Обновлено для 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)
Ответ 6
Разработайте минимальное примерное приложение, которое вы распространяете с помощью своих "абстрактных" моделей.
Предоставьте тесты для примера приложения, чтобы доказать абстрактные модели.
Ответ 7
Я пришел к этой проблеме, и мое решение находится на этом gist django-test-abstract-models
вы можете использовать его следующим образом:
1- подкласс ваших абстрактных моделей django
2- напишите свой тестовый пример следующим образом:
class MyTestCase(AbstractModelTestCase):
self.models = [MyAbstractModelSubClass, .....]
# your tests goes here ...
3, если вы не указали атрибут self.models
, он будет искать текущее приложение для моделей в пути myapp.tests.models.*
Ответ 8
Я думал, что смогу поделиться с вами своим решением, которое, на мой взгляд, намного проще, и я не вижу никаких минусов.
Пример идет для использования двух абстрактных классов.
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
Ответ 9
В 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 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) или аналогичному в других базах данных при выполнении других тестовых случаев. Что касается причины, по которой это происходит, я не знаю на момент написания.
ПРИМЕЧАНИЕ. Если у вас есть несколько абстрактных классов для тестирования, лучше использовать другие решения.
Ответ 10
Тестирование абстрактного класса не слишком полезно, так как производный класс может переопределять его методы. Другие приложения отвечают за тестирование своих классов на основе вашего абстрактного класса.