我需要为抽象基础模型编写一些单元测试,它提供了其他应用程序应该使用的一些基本功能。为了测试目的,有必要定义一个继承它的模型;是否有任何优雅/简单的方法来定义该模型仅用于测试?
我看到一些“黑客”使这成为可能,但从未在django文档或其他类似地方看到过“官方”方式。
答案 0 :(得分:17)
我自己偶然发现了这个功能:您可以在tests.py中继承您的抽象模型并像往常一样进行测试。当您运行'manage.py tests'时,Django不仅会创建一个测试数据库,还会验证&同步您的测试模型。
使用当前的Django trunk(版本1.2)进行测试。
答案 1 :(得分:10)
我也有同样的情况。我最终使用了@dylanboxalot解决方案的版本。在阅读'测试结构概述'后,专门从here获得了额外的详细信息。部分。
每次运行测试时都会调用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
答案 2 :(得分:8)
我最近偶然发现了这个并希望为更新的Django版本(1.9及更高版本)更新它。您可以使用SchemaEditor的create_model
而不是过时的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)
答案 3 :(得分:7)
我认为你要找的是something like this。
这是链接的完整代码:
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)
答案 4 :(得分:6)
已针对Django> = 2.0
更新所以我在使用m4rk4l的答案时遇到了一些问题:一个是在注释之一中出现的“ RuntimeWarning:模型'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)
答案 5 :(得分:2)
开发一个使用“抽象”模型分发的最小示例应用程序。 为示例应用程序提供测试以证明抽象模型。
答案 6 :(得分:1)
我自己解决了这个问题,我的解决方案就是这个要点django-test-abstract-models
你可以像这样使用它:
1-子类你的django抽象模型
2-写下你的测试用例:
class MyTestCase(AbstractModelTestCase):
self.models = [MyAbstractModelSubClass, .....]
# your tests goes here ...
3-如果您未提供self.models
属性,则会在当前应用中搜索路径myapp.tests.models.*
中的模型
答案 7 :(得分:0)
我认为我可以与您分享我的解决方案,我认为这要简单得多,而且我看不到任何弊端。
示例使用两个抽象类。
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
答案 8 :(得分:0)
在 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's answer的调整。一个显着的区别是我们使用setUpClass()
而不是setUpTestData()
。这种差异很重要,因为在运行其他测试用例时,使用后者会导致InterfaceError
(使用PostgreSQL时)或其他数据库中的等效项。至于发生这种情况的原因,在撰写本文时我还不知道。
注意::如果要测试多个抽象类,最好使用其他解决方案。
答案 9 :(得分:0)
我在这里尝试了解决方案,但遇到了类似问题
RuntimeWarning:模型'myapp .__ test__mymodel'已经注册
寻找如何使用pytest测试抽象模型也没有成功。我最终想出了一种最适合我的解决方案:
import tempfile
import pytest
from django.db import connection, models
from model_mommy import mommy
from ..models import AbstractModel
@pytest.fixture(scope='module')
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
class DummyModel(AbstractModel):
pass
class DummyImages(models.Model):
dummy = models.ForeignKey(
DummyModel, on_delete=models.CASCADE, related_name='images'
)
image = models.ImageField()
with connection.schema_editor() as schema_editor:
schema_editor.create_model(DummyModel)
schema_editor.create_model(DummyImages)
@pytest.fixture
def temporary_image_file():
image = tempfile.NamedTemporaryFile()
image.name = 'test.jpg'
return image.name
@pytest.mark.django_db
def test_fileuploader_model_file_name(temporary_image_file):
image = mommy.make('core.dummyimages', image=temporary_image_file)
assert image.file_name == 'test.jpg'
@pytest.mark.django_db
def test_fileuploader_model_file_mime_type(temporary_image_file):
image = mommy.make('core.dummyimages', image=temporary_image_file)
assert image.file_mime_type == 'image/jpeg'
如您所见,我定义了一个继承自Abstractmodel的Class,并将其添加为固定装置。 现在有了模型妈咪的灵活性,我可以创建一个DummyImages对象,它也会自动为我创建一个DummyModel!
或者,我可以通过不包含外键来简化示例,但是它很好地展示了pytest和model mommy组合的灵活性。
答案 10 :(得分:0)
这是django 3.0中使用Postgres的有效解决方案。它可以测试任意数量的抽象模型,并保持与异物有关的任何完整性。
from typing import Union
from django.test import TestCase
from django.db import connection
from django.db.models.base import ModelBase
from django.db.utils import ProgrammingError
# Category and Product are abstract models
from someApp.someModule.models import Category, Product, Vendor, Invoice
class MyModelsTestBase(TestCase):
@classmethod
def setUpTestData(cls):
# keep track of registered fake models
# to avoid RuntimeWarning when creating
# abstract models again in the class
cls.fake_models_registry = {}
def setUp(self):
self.fake_models = []
def tearDown(self):
try:
with connection.schema_editor(atomic=True) as schema_editor:
for model in self.fake_models:
schema_editor.delete_model(model)
except ProgrammingError:
pass
def create_abstract_models(self, models: Union[list, tuple]):
"""
param models: list/tuple of abstract model class
"""
# by keeping model names same as abstract model names
# we are able to maintain any foreign key relationship
model_names = [model.__name__ for model in models]
modules = [model.__module__ for model in models]
for idx, model_name in enumerate(model_names):
# if we already have a ModelBase registered
# avoid re-registering.
registry_key = f'{modules[idx]}.{model_name}'
model_base = self.fake_models_registry.get(registry_key)
if model_base is not None:
self.fake_models.append(model_base)
continue
# we do not have this model registered
# so register it and track it in our
# cls.fake_models_registry
self.fake_models.append(
ModelBase(
model_name,
(models[idx],),
{'__module__': modules[idx]}
)
)
self.fake_models_registry[registry_key] = self.fake_models[idx]
errors = []
# atomic=True allows creating multiple models in the db
with connection.schema_editor(atomic=True) as schema_editor:
try:
for model in self.fake_models:
schema_editor.create_model(model)
except ProgrammingError as e:
errors.append(e)
pass
return errors
def test_create_abstract_models(self):
abstract_models = (Category, Product)
errors = self.create_abstract_models(abstract_models)
self.assertEqual(len(errors), 0)
category_model_class, product_model_class = self.fake_models
# and use them like any other concrete model class:
category = category_model_class.objects.create(name='Pet Supplies')
product = product_model_class.objects.create(
name='Dog Food', category_id=category.id
)
答案 11 :(得分:0)
在阅读完以上所有答案后,我发现了在PostgreSQL 3.1.2和PostgreSQL 12.4数据库中对我有用的解决方案。
from django.db import connection
from django.db.utils import ProgrammingError
from django.test import TestCase
class AbstractModelTestCase(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):
if not hasattr(cls, "model"):
super(AbstractModelTestCase, cls).setUpClass()
else:
# 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(AbstractModelTestCase, cls).setUpClass()
except ProgrammingError:
pass
@classmethod
def tearDownClass(cls):
if hasattr(cls, "model"):
# Delete the schema for the test model
with connection.schema_editor() as schema_editor:
schema_editor.delete_model(cls.model)
super(AbstractModelTestCase, cls).tearDownClass()
它也摆脱了烦人的RuntimeWarning: Model 'xxx' was already registered
警告。
答案 12 :(得分:0)
Maikhoepfel's answer 是正确的,其他大多数看起来都不必要地复杂。我想提供进一步的说明,因为其他更复杂的答案似乎很受欢迎。
project/
├─ app1/
├─ app2/
│ ├─ tests/
│ │ ├─ __init__.py
│ │ ├─ models.py
│ │ ├─ test_models.py
│ ├─ __init__.py
│ ├─ apps.py
│ ├─ models.py
鉴于上述项目结构,app2.tests.models
中继承自 app2.models.YourAbstractModel
的模型将可用于任何测试(例如 app2.tests.test_models
),而无需运行迁移。>
这方面的例子可以在 Django test source code 中看到。
答案 13 :(得分:-4)
测试抽象类并不太有用,因为派生类可以覆盖其方法。其他应用程序负责根据您的抽象类测试它们的类。