如何使用默认和非托管数据库运行Django单元测试?

时间:2018-12-21 16:03:16

标签: python django django-unittest

我有一个带有默认数据库的Django项目,用于存储用户,订单等。

我们还有一个非托管数据库。现在,当您运行Django测试时,他们会尝试创建测试数据库,但是由于我们拥有不受管理的数据库,因此无法执行此操作。我无法创建此数据库的迁移,因为这将导致300个有关冲突反向访问器的错误。

我们使用Docker并自动启动此非托管数据库,并在其中填充一些模拟数据。这是用于开发等。我希望单元测试将其用于测试。

我尝试了诸如创建迁移之类的操作,但是由于反向访问器问题,这是不可能的。

是否可以使用非托管数据库进行单元测试? Django创建的test_default数据库很好,但是我无法创建test_unmanaged数据库。

1 个答案:

答案 0 :(得分:3)

我们使用在同一数据库中包含托管表和非托管表的设置,这也可能适用于您的用例:

我们有一个脚本可以从两个转储中生成测试数据库:test_structure.sqltest_fixtures.sql。前者包含某个时间点的数据库结构,包括所有非托管表。后者包含测试期间非托管表中可能需要的所有数据,以及django_migrations表的内容。我们使用生成的test_fixtures.sql列表转储COPY (SELECT * FROM {table}) TO STDOUT;语句,例如:COPY (SELECT * FROM obs_00.django_migrations) TO STDOUT WITH NULL '--null--';

使用以下函数将psql -c {copy_statement}的输出转换为INSERT语句:

def csv2sqlinsert(table_name, data):
    """
    Convert TSV output of  COPY (SELECT * FROM {table}) TO STDOUT
    to                     INSERT INTO {table} VALUES (), ()...();
    """

    def is_int(val):
        try:
            return "{}".format(int(val)) == val
        except ValueError:
            return False

    def column(data):
        if data == "--null--":
            return "null"
        elif is_int(data):
            return data
        else:
            return "'{}'".format(data.replace("'", "''"))  # escape quotes

    rows = [row.split("\t") for row in data.decode().split("\n") if len(row) > 1]

    if len(rows) == 0:
        return f"-- no data for {table_name}\n"

    data = ",\n".join("({})".format(",".join(column(col) for col in row)) for row in rows)

    ret = ""
    ret += f"-- {table_name} ({len(rows)} rows)\n"
    ret += f"INSERT INTO {table_name} VALUES\n{data};\n"

    return ret

实际上,此功能更为复杂,还简化了Postgis的几何形状,并截断了较大的文本字段以节省空间。

创建测试数据库

settings_test.py中定义测试数据库名称:

DATABASES["default"].update({
    "NAME": "django_test_db",
    "TEST": {"NAME": "django_test_db",},
})

使用上面的两个文件,(重新)创建测试数据库如下:

dropdb django_test_db
createdb django_test_db
psql -d django_test_db -f test_structure.sql
psql -d django_test_db < test_fixtures.sql

现在,我们具有转储时数据库的状态。因为可能会有新的迁移,所以我们让django迁移:

./manage.py migrate --settings=settings_test

运行测试

现在,我们可以使用./manage.py test --settings=settings_test运行测试。由于每次测试运行都会重新创建数据库,可能会花费大量时间,因此添加--keepdb将为您节省大量等待测试数据库还原过程的时间。

我们已经像这样修改manage.py,所以我们不能忘记:

#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "test":
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_test")
        cmd = sys.argv + ["--keepdb"]
    else:
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
        cmd = sys.argv

    from django.core.management import execute_from_command_line
    execute_from_command_line(cmd)