【问题标题】:How to create table during Django tests with managed = False如何在 Django 测试期间使用 managed = False 创建表
【发布时间】:2011-10-24 15:48:54
【问题描述】:

我有一个 managed = False 的模型。

class SampleModel(models.Model):
    apple = models.CharField(max_length=30)
    orange = models.CharField(max_length=30)

    class Meta:
        managed = False

我有一个创建 SampleModel 的单元测试,但是当我运行测试时,我得到:

DatabaseError: no such table: SAMPLE_SAMPLE_MODEL

django 文档 - https://docs.djangoproject.com/en/dev/ref/models/options/#managed 记录了以下内容:

对于涉及 managed=False 的模型的测试,您可以自行决定 确保在测试设置中创建了正确的表。

如何在测试设置期间实际“创建”表?或者,我怎样才能使它在我运行测试时,这个模型在测试期间具有“managed = True”?

在实际应用中,这个模型实际上是由数据库中的一个视图支持的。但是在测试期间,我想把它当作一个表格,并能够在其中插入测试数据。

【问题讨论】:

    标签: python django unit-testing


    【解决方案1】:

    查看这篇博文:http://www.caktusgroup.com/blog/2010/09/24/simplifying-the-testing-of-unmanaged-database-models-in-django/ 它详细描述了为非托管模型创建测试运行器。

    from django.test.simple import DjangoTestSuiteRunner
    
    
    class ManagedModelTestRunner(DjangoTestSuiteRunner):
        """
        Test runner that automatically makes all unmanaged models in your Django
        project managed for the duration of the test run, so that one doesn't need
        to execute the SQL manually to create them.
        """
        def setup_test_environment(self, *args, **kwargs):
            from django.db.models.loading import get_models
            self.unmanaged_models = [m for m in get_models()
                                     if not m._meta.managed]
            for m in self.unmanaged_models:
                m._meta.managed = True
            super(ManagedModelTestRunner, self).setup_test_environment(*args,
                                                                       **kwargs)
    
        def teardown_test_environment(self, *args, **kwargs):
            super(ManagedModelTestRunner, self).teardown_test_environment(*args,
                                                                          **kwargs)
            # reset unmanaged models
            for m in self.unmanaged_models:
                m._meta.managed = False
    

    【讨论】:

    • 它不适用于 Django 1.11(因为它将使用迁移来创建测试数据库,并且遵循您的模型定义将设置 managed = False)。 stackoverflow.com/a/37060122/462655 中提出的解决方案适用于 Django 1.11
    • FWIW 这也不适用于 Django 1.10.7(可能是整个 1.10.x 系列,但我不确定)。
    • 可以在dev.to/patrnk/testing-against-unmanaged-models-in-django获得对原始测试运行器想法的更新
    • 请把代码复制到这里,方便其他人在链接失效时找到。
    【解决方案2】:

    您可以在TestCase.setUp 方法中使用SchemaEditor 来显式创建带有managed = False 的模型。

    # models.py
    
    from django.db import models
    
    
    class Unmanaged(models.Model):
        foo = models.TextField()
    
        class Meta:
            # This model is not managed by Django
            managed = False
            db_table = 'unmanaged_table'
    

    在你的测试中:

    # tests.py
    
    from django.db import connection
    from django.test import TestCase
    
    from myapp.models import Unmanaged
    
    
    class ModelsTestCase(TestCase):
        def setUp(self):
            super().setUp()
    
            with connection.schema_editor() as schema_editor:
                schema_editor.create_model(Unmanaged)
    
                if Unmanaged._meta.db_table not in connection.introspection.table_names():
                    raise ValueError("Table `{table_name}` is missing in test database.".format(table_name=Unmanaged._meta.db_table))
    
        def tearDown(self):
            super().tearDown()
    
            with connection.schema_editor() as schema_editor:
                schema_editor.delete_model(Unmanaged)
    
        def test_unmanaged_model(self):
            with self.assertNumQueries(num=3):
                self.assertEqual(0, Unmanaged.objects.all().count())
                Unmanaged.objects.create()
                self.assertEqual(1, Unmanaged.objects.all().count())
    

    【讨论】:

    • 这比使用测试运行器、设置和迁移文件进行黑客攻击要干净得多。
    • 我同意,这似乎更干净。我使用旧版 mysql 数据库,其中包含许多非托管模型,其中一些由我的托管模型通过 OneToOne/ForeignKey 关系引用。我已经尝试过这个解决方案,但它似乎对我不起作用。我收到以下错误:django.db.utils.IntegrityError: (1215, 'Cannot add foreign key constraint')。
    【解决方案3】:

    执行原始 SQL 以在测试设置中创建表:

    from django.db import connection
    
    class MyTest(unittest.TestCase):
        def setUp(self):
            connection.cursor().execute("CREATE TABLE ...")
    
        def tearDown(self):
            connection.cursor().execute("DROP TABLE ...")
    

    【讨论】:

    • 您可以使用只包含表定义的转储命令,并在此答案的setUp() 方法中使用它。例如,在 MySQL 的情况下,命令类似于mysqldump --no-data databasename
    • CREATEDROP 每个测试的表都很慢。架构在测试之间永远不会改变。
    【解决方案4】:

    不错的即插即用解决方案。只需将其粘贴在您的测试类定义之前。 (注意:使用 django 1.8)

    from django.db.models.loading import get_models
    
    def change_managed_settings_just_for_tests():
      """django model managed bit needs to be switched for tests."""    
    
      unmanaged_models = [m for m in get_models() if not m._meta.managed]
      for m in unmanaged_models:
        m._meta.managed = True
    
    change_managed_settings_just_for_tests()
    

    【讨论】:

      【解决方案5】:

      使用这个创建你自己的测试运行器:

      from django.test.simple import DjangoTestSuiteRunner
      
      class NoDbTestRunner(DjangoTestSuiteRunner):
        """ A test runner to test without database creation """
      
        def setup_databases(self, **kwargs):
          """ Override the database creation defined in parent class """
          #set manage=True for that specific database on here
      

      然后在您的设置中将此类添加到 TEST_RUNNER。

      【讨论】:

        【解决方案6】:

        如果您没有很多非托管表,则可以快速解决:

        首先在设置中添加一个新变量。

        # settings.py
        import sys
        UNDER_TEST = (len(sys.argv) > 1 and sys.argv[1] == 'test')
        

        然后在模型中

        # models.py
        from django.conf import settings
        
        class SampleModel(models.Model):
            apple = models.CharField(max_length=30)
            orange = models.CharField(max_length=30)
        
            class Meta:
                managed = getattr(settings, 'UNDER_TEST', False)
        

        【讨论】:

          【解决方案7】:

          只需添加 :django.db.models.loading.get_models 将在 Django 1.9 中删除(请参阅https://github.com/BertrandBordage/django-cachalot/issues/33)。

          以下是 Django 1.10 的更新:

          class UnManagedModelTestRunner(DiscoverRunner):
              '''
              Test runner that automatically makes all unmanaged models in your Django
              project managed for the duration of the test run.
              Many thanks to the Caktus Group 
              '''
          
              def setup_test_environment(self, *args, **kwargs):
                  from django.apps  import apps
                  self.unmanaged_models = [m for m in apps.get_models() if not m._meta.managed]
                  for m in self.unmanaged_models:
                      m._meta.managed = True
                  super(UnManagedModelTestRunner, self).setup_test_environment(*args, **kwargs)
          
              def teardown_test_environment(self, *args, **kwargs):
                  super(UnManagedModelTestRunner, self).teardown_test_environment(*args, **kwargs)
                  # reset unmanaged models
                  for m in self.unmanaged_models:
                      m._meta.managed = False 
          

          请注意,您还需要小心迁移(请参阅Testing django application with several legacy databases

          MIGRATION_MODULES = {
              'news': 'news.test_migrations',
              'economist': 'economist.test_migrations'
          }
          

          【讨论】:

            【解决方案8】:

            使用 pytest 和 pytest-django

            为了完成这项工作(已使用 django 3.0.2pytest 5.3.5pytest-django 3.8.0 进行了测试) :

            1. 使用附加参数 --no-migrations 运行 pytest。
            2. 将以下代码放入您的 conftest.py 中。这有大量的copypasta,但我不知道如何首先使我的模型不受管理,然后调用原始的django_db_setup。无法直接调用pytest fixture的问题在这里讨论:https://github.com/pytest-dev/pytest/issues/3950

            conftest.py

            # example file
            import pytest
            from pytest_django.fixtures import _disable_native_migrations
            
            
            @pytest.fixture(scope="session")
            def django_db_setup(
                    request,
                    django_test_environment,
                    django_db_blocker,
                    django_db_use_migrations,
                    django_db_keepdb,
                    django_db_createdb,
                    django_db_modify_db_settings,
            ):
                # make unmanaged models managed
                from django.apps import apps
                unmanaged_models = []
                for app in apps.get_app_configs():
                    unmanaged_models = [m for m in app.get_models()
                                        if not m._meta.managed]
                for m in unmanaged_models:
                    m._meta.managed = True
            
                # copypasta fixture code
            
                """Top level fixture to ensure test databases are available"""
                from pytest_django.compat import setup_databases, teardown_databases
            
                setup_databases_args = {}
            
                if not django_db_use_migrations:
                    _disable_native_migrations()
            
                if django_db_keepdb and not django_db_createdb:
                    setup_databases_args["keepdb"] = True
            
                with django_db_blocker.unblock():
                    db_cfg = setup_databases(
                        verbosity=request.config.option.verbose,
                        interactive=False,
                        **setup_databases_args
                    )
            
                def teardown_database():
                    with django_db_blocker.unblock():
                        try:
                            teardown_databases(db_cfg, verbosity=request.config.option.verbose)
                        except Exception as exc:
                            request.node.warn(
                                pytest.PytestWarning(
                                    "Error when trying to teardown test databases: %r" % exc
                                )
                            )
            
                if not django_db_keepdb:
                    request.addfinalizer(teardown_database)
            

            【讨论】:

            • 实际上,对于解决同样的问题,这是一个比我更好的答案:stackoverflow.com/a/50849037/3193371
            • 我猜你想扩展非托管模型列表而不是替换它:unmanaged_models += [m for m in app.get_models() if not m._meta.managed]
            【解决方案9】:

            在花了几个小时测试和研究测试我的 django 非托管模型的方法后,我终于想出了一个适合我的解决方案。
            我的实现在下面的 sn-p 中。它非常适合使用 db.sqlite3 进行本地测试。

            # Example classes Implementation
            
            from django.db import models, connection
            from django.db.models.base import ModelBase as DjangoModelBase
            from django.db.utils import OperationalError
            from django.test import TransactionTestCase
            
            
            class AbstractModel(models.Model):
                name = models.TextField(db_column="FIELD_NAME", blank=True, null=True)
            
                class Meta:
                    managed = False
                    abstract = True
            
            
            class TestModel(AbstractModel):
                test_field = models.TextField(db_column="TEST_FIELD", blank=True, null=True)
            
                def test_method(self):
                    print("just testing")
            
                class Meta(AbstractModel.Meta):
                    db_table = "MY_UNMANAGED_TABLE_NAME"
            
            
            
            # My Custom Django TestCases Implementation for my tests
            
            def create_database(model):
                with connection.schema_editor() as schema_editor:
                    try:
                        schema_editor.create_model(model)
                    except OperationalError:
                        pass
            
            
            def drop_database(model):
                with connection.schema_editor() as schema_editor:
                    try:
                        schema_editor.delete_model(model)
                    except OperationalError:
                        pass
            
            
            class BaseModelTestCase(TransactionTestCase):
                """Custom TestCase for testing models not managed by django."""
            
                Model = DjangoModelBase
            
                def setUp(self):
                    super().setUp()
                    create_database(self.Model)
            
                def tearDown(self):
                    super().tearDown()
                    drop_database(self.Model)
            
            
            class AbstractBaseModelTestCase(TransactionTestCase):
                """Custom TestCase for testing abstract django models."""
            
                Model = DjangoModelBase
            
                def setUp(self):
                    # this is necessary for testing an abstract class
                    self.Model = DjangoModelBase(
                        "__TestModel__" + self.Model.__name__,
                        (self.Model,),
                        {"__module__": self.Model.__module__},
                    )
                    create_database(self.Model)
            
                def tearDown(self):
                    drop_database(self.Model)
            
            
            
            # Example of usage
            
            class TestModelTestCase(BaseModelTestCase):
                Model = TestModel
            
                def setUp(self):
                    super().setUp()
                    self.instance = TestModel.objects.create()
                    self.assertIsInstance(self.instance, TestModel)
            
            
            class AbstractModelTestCase(AbstractBaseModelTestCase):
                Model = AbstractModel
            
                def setUp(self):
                    super().setUp()
                    self.instance = AbstractModel.objects.create()
                    self.assertIsInstance(self.instance, AbstractModel)
            

            附言只要我的模型是managed=False,我就不会使用任何 Django 迁移。所以这对我来说没有必要。

            【讨论】:

              【解决方案10】:

              只需将非托管模型移动到专用应用并删除迁移文件夹即可。我在Multi db and unmanged models - Test case are failing的回答中的详细信息

              【讨论】:

                猜你喜欢
                • 2015-05-02
                • 2019-04-16
                • 2021-04-06
                • 1970-01-01
                • 2012-07-28
                • 2017-04-10
                • 2020-07-14
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多