我需要为抽象基础模型编写一些单元测试,它提供了其他应用程序应该使用的一些基本功能。为了测试目的,有必要定义一个继承它的模型;是否有任何优雅/简单的方法来定义该模型仅用于测试?
我看到一些“黑客”使这成为可能,但从未在django文档或其他类似的地方看到过“官方”方式。
我自己偶然发现了这个功能:您可以在tests.py中继承您的抽象模型并像往常一样进行测试。当您运行'manage.py tests'时,Django不仅会创建测试数据库,还会验证和同步您的测试模型。
用当前的Django trunk(版本1.2)测试它。
测试抽象类并不太有用,因为派生类可以覆盖其方法。其他应用程序负责根据您的抽象类测试其类。
我也有同样的情况。我最终使用了@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
我最近偶然发现了这个并希望为更新的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)
我认为你在寻找的是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)
更新了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)
开发一个使用“抽象”模型分发的最小示例应用程序。为示例应用程序提供测试以证明抽象模型。
我自己解决了这个问题,我的解决方案是在这个问题上django-test-abstract-models
你可以像这样使用它:
1-子类你的django抽象模型
2-编写你的测试用例如下:
class MyTestCase(AbstractModelTestCase):
self.models = [MyAbstractModelSubClass, .....]
# your tests goes here ...
3-如果你没有提供self.models
属性,它将搜索当前应用程序中的路径myapp.tests.models.*
中的模型
我想我可以与你分享我的解决方案,这在我看来简单得多,我没有看到任何缺点。
示例适用于使用两个抽象类。
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
在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时)或其他测试用例运行时在其他数据库中的等价物。至于发生这种情况的原因,我在编写时并不知道。
注意:如果要测试多个抽象类,最好使用其他解决方案。