【问题标题】:Missing Table When Running Django Unittest with Sqlite3使用 Sqlite3 运行 Django Unittest 时缺少表
【发布时间】:2011-11-12 06:17:51
【问题描述】:

我正在尝试使用 Django 1.3 运行单元测试。通常,我使用 MySQL 作为我的数据库后端,但由于单个单元测试的启动速度非常慢,所以我使用的是 Sqlite3。

所以为了我的单元测试切换到 Sqlite3,在我的 settings.py 中我有:

import sys
if 'test' in sys.argv:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME':'/tmp/database.db',
            'USER'       : '',
            'PASSWORD' : '',
            'HOST'     : '',
        }
    }

当我使用 python manage.py test myapp.Test.test_myfunc 运行我的单元测试时,我得到了错误:

DatabaseError: no such table: django_content_type

谷歌搜索显示有一个fewpossible reasons 对于这个error,似乎都不适合我。我没有运行 Apache,所以我看不出权限会是什么问题。正在创建文件 /tmp/database.db,因此 /tmp 是可写的。应用程序 django.contrib.contenttypes 包含在我的 INSTALLED_APPS 中。

我错过了什么?

编辑:我在 Django 1.5 中再次遇到了这个问题,但建议的解决方案都不起作用。

【问题讨论】:

    标签: python database django unit-testing sqlite


    【解决方案1】:

    通过执行单元测试在数据库中找不到您的表,因为单元测试继承了 unittest.TestCase。其中不要将迁移应用到您的数据库。

    如果您想使用迁移后更改的数据库,那么您应该从 django.test.TestCase 继承测试用例类(例如)。

    运行python manage.py test -v 3,您将看到应用迁移到默认测试数据库。

    更多信息至:https://docs.djangoproject.com/en/dev/topics/testing/overview/#the-test-database

    【讨论】:

      【解决方案2】:

      发生此错误的原因有很多,对我而言,这与骇人听闻的数据迁移有关。

      这些数据迁移对需要尚未创建的字段的模型进行了一些操作。

      例如

      0001_accounts.py {create the model User}
      0002_accounts.py {{for user in User.objects.all(): user.save() }
      0003_accounts.py {add a new column to User object}
      

      它在 0002 上失败,因为它试图保存用户对象但还没有新列。

      调试:

      switch to an empty database
      run python manage.py migrate
      

      看看它在哪里停止,这可能是您想要取消注释的骇人听闻的数据迁移。

      【讨论】:

        【解决方案3】:

        我遇到了类似的问题,我是由以前的分支代码引起的,所以我修复了它,删除了我的项目中的 pyc 文件:

        find -regex .*pyc | xargs sudo rm
        

        【讨论】:

          【解决方案4】:

          在尝试了以上所有方法后,我最终发现了可能发生这种情况的另一个原因:-

          如果您的任何模型不是由您的迁移之一创建的。

          我做了一些调试,似乎 Django 测试通过按顺序应用所有迁移来设置数据库,从 001_initial.py 开始,然后尝试根据您的 models.py

          在我的情况下,不知何故未将表添加到迁移中,而是手动添加,因此无法正确应用完整的迁移集。当我手动修复 001_initial.py 迁移以创建此表时,OperationalError 消失了。

          【讨论】:

          • 我遇到了类似的问题,这尤其令人困惑,因为“丢失”的项目都在我的 models.py 中,所以它们不会出现在 test_db 中真是令人困惑。结果发现,当我进行更改时,我忘记了从我的开发实例中提交一些迁移。
          【解决方案5】:

          对于任何会到这里的人来说,搜索为什么 Django 不断创建数据库而不考虑--keepdb 选项。为什么它说Using existing test database for alias 'default',然后运行一堆CREATE TABLE 语句。

          如果你没有设置DATABASES > default > TEST > NAME 设置,Django 将尝试在内存数据库中使用,并且不会保留,所以设置它并覆盖默认值。

          你可以这样:

          DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(ROOT_DIR, 'data', 'db.dev.sqlite3'), 'TEST': { 'NAME': os.path.join(ROOT_DIR, 'data', 'db.test.sqlite3'), } } }

          【讨论】:

            【解决方案6】:

            只是为了添加另一个案例:

            如果您尝试从 1.6 升级到 1.8(或从非迁移设置升级到迁移设置),如果您尚未运行创建的迁移,则可能会遇到此错误。

            我遇到了同样的问题,必须创建迁移,以便测试运行程序可以使用它们,这并不直观,因为预迁移,测试只会基于执行 syncdb 创建一个新数据库,这总是有效的。

            【讨论】:

              【解决方案7】:

              对于那些尝试了所有可能的方法但仍然坚持这一点的人:

              由于我们的测试代码仍在其他机器上运行,但不是我的,我尝试:

              • 创建一个新的虚拟环境。 (因此隔离应用的影响)
              • 克隆一个新的存储库。 (隔离被忽略文件的影响)
              • settings 中设置使用sqlite3 而不是psql(隔离数据库和数据库设置的影响)
              • 检查env变量和.env文件(因为我使用了foreman

              这些都没有帮助。直到我:

              1. 在我的机器上创建一个新帐户。 (所以开始清洁)
              2. 克隆存储库。
              3. 运行测试 -> 成功?
              4. 返回我的主帐户。
              5. 打开一个新终端(如果您正在使用它们,请杀死当前的tmux 服务器)。
              6. 运行测试 -> 成功?

              所以这不是您问题的真正答案,因为我不知道出了什么问题以及如何解决,只是您可以尝试的另一个建议。

              【讨论】:

                【解决方案8】:

                在 Django 1.4、1.5、1.6、1.7 或 1.8 中,应该足以使用:

                if 'test' in sys.argv:
                    DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
                

                不必重写TEST_NAME1,也不必调用syncdb 来运行测试。正如@osa 指出的那样,SQLite 引擎的默认设置是在内存中创建测试数据库 (TEST_NAME=':memory:')。调用 syncdb 应该不是必需的,因为 Django 的测试框架将通过调用 syncdbmigrate 自动执行此操作,具体取决于 Django 版本。2您可以使用 manage.py test -v [2|3] 观察这一点。

                非常松散地说 Django 通过以下方式设置测试环境:

                1. 从您的settings.py 加载常规数据库NAME
                2. 发现并构建您的测试类(调用__init__()
                3. 将数据库NAME 设置为TEST_NAME 的值
                4. 针对数据库 NAME 运行测试

                问题来了:在第 2 步,NAME 仍然指向您的常规(非测试)数据库。如果您的测试包含类级查询或__init__() 中的查询,它们将针对常规数据库运行,这可能不是您所期望的。这在bug #21143 中标识。

                不要这样做:

                class BadFooTests(TestCase):
                    Foo.objects.all().delete()     # <-- class level queries, and
                
                    def __init__(self):
                        f = Foo.objects.create()   # <-- queries in constructor
                        f.save()                   #     will run against the production DB
                
                    def test_foo(self):
                        # assert stuff
                

                因为这些将针对NAME 中指定的数据库运行。如果在此阶段NAME 指向一个有效的数据库(例如您的生产数据库),则查询将运行,但可能会产生意想不到的后果。如果您已覆盖 ENGINE 和/或 NAME 使其不指向预先存在的数据库,则会引发异常,因为尚未创建测试数据库:

                django.db.utils.DatabaseError: no such table: yourapp_foo  # Django 1.4
                DatabaseError: no such table: yourapp_foo                  # Django 1.5
                OperationalError: no such table: yourapp_foo               # Django 1.6+
                

                改为:

                class GoodFooTests(TestCase):
                
                    def setUp(self):
                        f = Foo.objects.create()   # <-- will run against the test DB
                        f.save()                   #
                
                    def test_foo(self):
                        # assert stuff
                

                因此,如果您看到错误,请检查您的测试是否包含任何可能在您的测试类方法定义之外访问数据库的查询。


                [1] 在 Django >= 1.7 中,DATABASES[alias]['TEST_NAME']deprecated 支持 DATABASES[alias]['TEST']['NAME']
                [2] 见db/backends/creation.py中的create_test_db()方法

                【讨论】:

                • 刚升级到1.7,现在django.contrib.sites出现这个错误,你提供的解决方案不起作用。
                • 就我而言,由于我们没有使用内置迁移功能,只需从应用程序中删除迁移文件夹就可以了
                【解决方案9】:

                我必须在测试数据库定义后添加以下几行:

                from django.core.management import call_command
                call_command('syncdb', migrate=True)
                

                【讨论】:

                • 这不适用于 1.7(可能不适用于 >= 1.5)
                【解决方案10】:

                为了将来参考,如果您的应用程序未添加到您的INSTALLED_APPS,也会发生这种情况,例如:

                INSTALLED_APPS = (
                   ...
                   'myapp'
                )
                

                否则你会得到;

                OperationalError: no such table: myapp_mytable
                

                【讨论】:

                  【解决方案11】:

                  我也有这个问题。原来我必须在 settings.py 文件中添加一个 TEST_NAME 属性才能正确识别测试数据库。它为我解决了问题:

                  if 'test' in sys.argv:
                      DATABASES = {
                          'default': {
                              'ENGINE': 'django.db.backends.sqlite3',
                              'NAME': os.path.join(os.path.dirname(__file__), 'test.db'),
                              'TEST_NAME': os.path.join(os.path.dirname(__file__), 'test.db'),
                         }
                      }
                  

                  【讨论】:

                  • 这非常接近我的实际解决方案。
                  • 这很有帮助!如果指定了TEST_NAME,Django 会将测试数据库放在一个文件中,而不是将其保存在内存中。因此,我可以暂停测试过程并使用sqlite3 test.db 从另一个终端打开文件。我认为NAME 对于测试配置根本没有用。 TEST_NAME 可能就是我们所需要的。
                  • 事实上,我的结论是我的测试适用于磁盘数据库,但不适用于内存数据库。
                  • 我最近又遇到了这个问题,现在在 Django 1.5 上,这个解决方案不起作用。我只是收到错误“没有这样的表 myapp_mymodel”。
                  【解决方案12】:

                  您的数据库可能是空的,必须使用与您的模型对应的所有表进行设置。通常,这将通过首先运行python manage.py syncdb 来完成,以创建所有数据库表。问题是,在您的情况下,当您运行 syncdb 时,python 不会看到您正在运行测试,因此它将尝试在您的 MySQL 数据库中设置表。

                  为了解决这个问题,暂时改变

                  if 'test' in sys.argv:
                  

                  if True:
                  

                  然后运行python manage.py syncdb 来设置sqlite 数据库表。现在一切都设置好了,你可以放回if 'test'...,一切都应该运行顺利。但是,您可能希望将数据库移出/tmp 目录:django 需要在每次运行测试时重复使用同一个数据库,否则您必须在每次测试之前创建数据库表。

                  请注意,如果添加新模型,则需要重复此过程以在 sqlite 中创建新表。如果向现有模型添加新字段,则需要使用 sqlite 接口和 ALTER TABLE... 手动将列​​添加到 sqlite 数据库,或者使用 South. 等工具自动添加

                  【讨论】:

                  • 我怀疑这会奏效。 Django 每次运行后都会破坏测试数据库,所以如果你是正确的,我需要在每次运行之前做一个 syncdb...
                  • 它确实有效。 test-Sqlite 数据库是in-memory by default,所以测试后没有要销毁的文件。您使用 syncdb 创建的文件仅充当内存测试数据库的模板。只有当数据库结构发生变化时才需要同步数据库。
                  • 一个最荒谬的结构! if True 将始终执行,那么为什么要浪费时间检查它呢?
                  猜你喜欢
                  • 2012-06-06
                  • 1970-01-01
                  • 2017-12-05
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-05-07
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多