运行Django单元测试时缺少SQLite3表

21 投票
12 回答
13379 浏览
提问于 2025-04-17 01:42

我正在尝试在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

我在网上搜索后发现,有一些可能的 原因 导致这个错误,但这些似乎都不适用于我。我没有运行Apache,所以我觉得权限问题不太可能。文件/tmp/database.db已经被创建,所以/tmp是可以写入的。我的INSTALLED_APPS中包含了django.contrib.contenttypes这个应用。

我漏掉了什么呢?

补充:我在Django 1.5中又遇到了这个问题,但没有一个建议的解决方案有效。

12 个回答

4

我也遇到过这个问题。后来发现我需要在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'),
       }
    }
5

经过尝试了以上所有方法,我最终发现了另一个可能导致这个问题的原因:

如果你的某些模型不是通过迁移文件创建的。

我做了一些调试,发现Django在测试时会按照顺序应用所有的迁移文件来设置数据库,首先是001_initial.py,然后再根据你的models.py去从表中选择数据。

在我的情况下,有一个表不知怎么的没有被加入到迁移文件中,而是手动添加的,所以完整的迁移集无法正确应用。当我手动修复了001_initial.py迁移文件,使其创建了这个表后,OperationalError错误就消失了。

15

在Django的1.4到1.8版本中,使用以下内容就应该足够了:

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

你不需要重写TEST_NAME1,也不需要调用syncdb来运行测试。正如@osa所指出的,使用SQLite数据库时,默认会在内存中创建测试数据库(TEST_NAME=':memory:')。调用syncdb其实是不必要的,因为Django的测试框架会根据不同的Django版本自动处理这个过程,可能会调用syncdbmigrate2你可以通过manage.py test -v [2|3]来观察这一点。

简单来说,Django通过以下步骤来设置测试环境:

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

这里有个问题:在第二步时,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']已经被弃用,取而代之的是DATABASES[alias]['TEST']['NAME']
[2] 请查看db/backends/creation.py中的create_test_db()方法

撰写回答