测试抽象模型- django 2.2.4 / sqlite3 2.6.0

yqyhoc1h  于 2023-10-23  发布在  SQLite
关注(0)|答案(2)|浏览(200)

我正在尝试使用django 2.2.4/sqlite3 2.6.0/python 3.6.8测试一些简单的抽象mixin。
目前,我在使用模式编辑器从测试数据库中删除模型时遇到了问题。
我有以下测试用例:

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

class ModelMixinTestCase(TestCase):
    """
    Test Case for abstract mixin models.
    """
    mixin = None
    model = None

    @classmethod
    def setUpClass(cls) -> None:
        # Create a real model from the mixin
        cls.model = ModelBase(
            "__Test" + cls.mixin.__name__,
            (cls.mixin,),
            {'__module__': cls.mixin.__module__}
        )

        # Use schema_editor to create schema
        with connection.schema_editor() as editor:
            editor.create_model(cls.model)

        super().setUpClass()

    @classmethod
    def tearDownClass(cls) -> None:
        # Use schema_editor to delete schema
        with connection.schema_editor() as editor:
            editor.delete_model(cls.model)

        super().tearDownClass()

可以这样使用:

class MyMixinTestCase(ModelMixinTestCase):
    mixin = MyMixin

    def test_true(self):
        self.assertTrue(True)

这允许创建和测试模型。问题是在ModelMixinTestCase.tearDownClass中,connection.schema_editor()无法禁用django.db.backends.sqlite3.base中使用的约束检查:

def disable_constraint_checking(self):
        with self.cursor() as cursor:
            cursor.execute('PRAGMA foreign_keys = OFF')
            # Foreign key constraints cannot be turned off while in a multi-
            # statement transaction. Fetch the current state of the pragma
            # to determine if constraints are effectively disabled.
            enabled = cursor.execute('PRAGMA foreign_keys').fetchone()[0]
        return not bool(enabled)

这导致django.db.backends.sqlite3.schema中的DatabaseSchemaEditor__enter__中出现异常:

def __enter__(self):
        # Some SQLite schema alterations need foreign key constraints to be
        # disabled. Enforce it here for the duration of the schema edition.
        if not self.connection.disable_constraint_checking():
            raise NotSupportedError(
                'SQLite schema editor cannot be used while foreign key '
                'constraint checks are enabled. Make sure to disable them '
                'before entering a transaction.atomic() context because '
                'SQLite does not support disabling them in the middle of '
                'a multi-statement transaction.'
            )
        return super().__enter__()

因此,基于所有这些,我假设我们处于原子上下文中,但我目前不确定退出该上下文并删除模型的最干净方法是什么。

eufgjt7s

eufgjt7s1#

简单解决方案

因此,经过一点挖掘和测试,似乎最好让django的TestCase正常关闭事务,然后从测试数据库中删除模型。基本上,我们只是先调用super().tearDownClass()而不是last。

ModelMixinTestCase

因为这是一个有用的类,我将发布完整的类供其他人复制/粘贴。

class ModelMixinTestCase(TestCase):
    """
    Test Case for abstract mixin models.
    Subclass and set cls.mixin to your desired mixin.
    access your model using cls.model.
    """
    mixin = None
    model = None

    @classmethod
    def setUpClass(cls) -> None:
        # Create a real model from the mixin
        cls.model = ModelBase(
            "__Test" + cls.mixin.__name__,
            (cls.mixin,),
            {'__module__': cls.mixin.__module__}
        )

        # Use schema_editor to create schema
        with connection.schema_editor() as editor:
            editor.create_model(cls.model)

        super().setUpClass()

    @classmethod
    def tearDownClass(cls) -> None:
        # allow the transaction to exit
        super().tearDownClass()

        # Use schema_editor to delete schema
        with connection.schema_editor() as editor:
            editor.delete_model(cls.model)

        # close the connection
        connection.close()

示例用法1

class MyMixinTestCase(ModelMixinTestCase):
    mixin = MyMixin

    def test_true(self):
        self.assertTrue(True)

示例用法二

class SortableModelMixinTestCase(ModelMixinTestCase):
    mixin = SortableModelMixin

    def setUp(self) -> None:
        self.objects = [self.model.objects.create(pk=i) for i in range(10)]

    def test_order_linear(self) -> None:
        i = 1
        for item in self.objects:
            self.assertEqual(i, item.sortable_order)
            i += 1
s2j5cfk0

s2j5cfk02#

@bob谢谢你的好办法,效果很好。
但是当在具有相同模型的不同TestCase上重用mixin时,我遇到了一个警告:

RuntimeWarning: Model '<model_name>' was already registered. Reloading models is not advised as it can lead to inconsistencies, most notably with related models.

我通过检查模型是否已经在应用程序中注册来解决这个问题:

@classmethod
def setUpClass(cls) -> None:
    # Create a real model from the mixin or
    # get it from the reqistered models if it already exist
    model_name = "__Test" + cls.mixin.__name__
    for app, models in apps.all_models.items():
        if model_name.lower() in models:
            cls.model = models[model_name.lower()]
    if not hasattr(cls, "model"):
        cls.model = ModelBase(
            model_name, (cls.mixin,),
            {'__module__': cls.mixin.__module__}
        )

相关问题