Django:对抽象模型进行单元测试的最佳方法

问题描述 投票:20回答:9

我需要为抽象基础模型编写一些单元测试,它提供了其他应用程序应该使用的一些基本功能。为了测试目的,有必要定义一个继承它的模型;是否有任何优雅/简单的方法来定义该模型仅用于测试?

我看到一些“黑客”使这成为可能,但从未在django文档或其他类似的地方看到过“官方”方式。

django unit-testing django-models abstract
9个回答
17
投票

我自己偶然发现了这个功能:您可以在tests.py中继承您的抽象模型并像往常一样进行测试。当您运行'manage.py tests'时,Django不仅会创建测试数据库,还会验证和同步您的测试模型。

用当前的Django trunk(版本1.2)测试它。


-4
投票

测试抽象类并不太有用,因为派生类可以覆盖其方法。其他应用程序负责根据您的抽象类测试其类。


10
投票

我也有同样的情况。我最终使用了@dylanboxalot解决方案的版本。在阅读“测试结构概述”部分后,获得了here的额外详细信息。

每次运行测试时都会调用setUptearDown方法。更好的解决方案是在运行所有测试之前运行一次“抽象”模型的创建。为此,您可以实现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
投票

我最近偶然发现了这个并希望为更新的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)

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)                                 

7
投票

更新了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)

2
投票

开发一个使用“抽象”模型分发的最小示例应用程序。为示例应用程序提供测试以证明抽象模型。


2
投票

我自己解决了这个问题,我的解决方案是在这个问题上django-test-abstract-models

你可以像这样使用它:

1-子类你的django抽象模型

2-编写你的测试用例如下:

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

3-如果你没有提供self.models属性,它将搜索当前应用程序中的路径myapp.tests.models.*中的模型


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

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时)或其他测试用例运行时在其他数据库中的等价物。至于发生这种情况的原因,我在编写时并不知道。

注意:如果要测试多个抽象类,最好使用其他解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.