Django单元测试需要很长时间才能创建测试数据库



一段时间以来,我的单元测试花费的时间比预期的要长。我试过几次调试它,但都没有成功,因为延迟是在我的测试开始运行之前。这影响了我做任何接近测试驱动开发的远程工作的能力(也许我的期望值太高了),所以我想看看我是否能一劳永逸地解决这个问题。

当运行测试时,测试的开始和实际开始之间有70到80秒的延迟。例如,如果我运行一个小模块的测试(使用time python manage.py test myapp),我会得到

<... bunch of unimportant print messages I print from my settings>
Creating test database for alias 'default'...
......
----------------------------------------------------------------
Ran 6 tests in 2.161s
OK
Destroying test database for alias 'default'...
real    1m21.612s
user    1m17.170s
sys     0m1.400s

1m:21中约1m18位于之间

Creating test database for alias 'default'...

.......

行。换句话说,测试耗时不到3秒,但数据库初始化似乎需要1:18min的

我有大约30个应用程序,大多数都有1到3个数据库模型,所以这应该能说明项目的规模。我使用SQLite进行单元测试,并实现了一些建议的改进。我不能发布我的整个设置文件,但很乐意添加任何需要的信息。

我确实使用跑步

from django.test.runner import DiscoverRunner
from django.conf import settings
class ExcludeAppsTestSuiteRunner(DiscoverRunner):
    """Override the default django 'test' command, exclude from testing
    apps which we know will fail."""
    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        if not test_labels:
            # No appnames specified on the command line, so we run all
            # tests, but remove those which we know are troublesome.
            test_labels = (
                'app1',
                'app2',
                ....
                )
            print ('Testing: ' + str(test_labels))
        return super(ExcludeAppsTestSuiteRunner, self).run_tests(
                test_labels, extra_tests, **kwargs)

在我的设置中:

TEST_RUNNER = 'config.test_runner.ExcludeAppsTestSuiteRunner'

我还尝试过将django-nosedjango-nose-exclude 一起使用

我读了很多关于如何加快测试速度的文章,但没有找到任何关于如何优化或避免数据库初始化的线索。我看到了关于尽量不使用数据库进行测试的建议,但我无法或不知道如何完全避免这种情况。

如果

  1. 这是正常的,也是意料之中的
  2. 没有预料到(希望能解决或引导该怎么做)

同样,我不需要关于如何加快测试本身的帮助,而是需要初始化(或开销)的帮助。我希望上面的例子用10秒而不是80秒。

非常感谢

我用--verbose 3运行了测试(针对单个应用程序),发现这一切都与迁移有关:

  Rendering model states... DONE (40.500s)
  Applying authentication.0001_initial... OK (0.005s)
  Applying account.0001_initial... OK (0.022s)
  Applying account.0002_email_max_length... OK (0.016s)
  Applying contenttypes.0001_initial... OK (0.024s)
  Applying contenttypes.0002_remove_content_type_name... OK (0.048s)
  Applying s3video.0001_initial... OK (0.021s)
  Applying s3picture.0001_initial... OK (0.052s)
  ... Many more like this

我完成了所有的迁移,但仍然很慢。

修复我的问题的最终解决方案是在测试期间强制Django禁用迁移,这可以从类似的设置中完成

TESTING = 'test' in sys.argv[1:]
if TESTING:
    print('=========================')
    print('In TEST Mode - Disableling Migrations')
    print('=========================')
    class DisableMigrations(object):
        def __contains__(self, item):
            return True
        def __getitem__(self, item):
            return None
    MIGRATION_MODULES = DisableMigrations()

或使用https://pypi.python.org/pypi/django-test-without-migrations

我的整个测试现在大约需要1分钟,一个小应用程序需要5秒。

在我的情况下,测试不需要迁移,因为我在迁移时更新测试,并且不使用迁移来添加数据。这不适用于所有人

摘要

使用pytest

操作

  1. pip install pytest-django
  2. pytest --nomigrations而不是./manage.py test

结果

  • ./manage.py test花费2分11.86秒
  • pytest --nomigrations花费2.18秒

提示

  • 您可以在项目根目录中创建一个名为pytest.ini的文件,并在其中指定默认的命令行选项和/或Django设置。

    # content of pytest.ini
    [pytest]
    addopts = --nomigrations
    DJANGO_SETTINGS_MODULE = yourproject.settings
    

    现在,您可以简单地使用pytest运行测试,并节省一些键入的时间。

  • 通过将--reuse-db添加到默认命令行选项中,可以进一步加快后续测试的速度。

    [pytest]
    addopts = --nomigrations --reuse-db
    

    但是,一旦数据库模型发生更改,就必须运行pytest --create-db一次,以强制重新创建测试数据库。

  • 如果在测试期间需要启用geventmonkey补丁,可以在项目根目录中创建一个名为pytest的文件,其中包含以下内容,将执行位强制转换为chmod +x pytest,然后运行./pytest而不是pytest:进行测试

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # content of pytest
    from gevent import monkey
    monkey.patch_all()
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "yourproject.settings")
    from django.db import connection
    connection.allow_thread_sharing = True
    import re
    import sys
    from pytest import main
    if __name__ == '__main__':
        sys.argv[0] = re.sub(r'(-script.pyw|.exe)?$', '', sys.argv[0])
        sys.exit(main())
    

    您可以创建一个test_gevent.py文件来测试gevent monkey补丁是否成功:

    # -*- coding: utf-8 -*-
    # content of test_gevent.py
    import time
    from django.test import TestCase
    from django.db import connection
    import gevent
    
    def f(n):
        cur = connection.cursor()
        cur.execute("SELECT SLEEP(%s)", (n,))
        cur.execute("SELECT %s", (n,))
        cur.fetchall()
        connection.close()
    
    class GeventTestCase(TestCase):
        longMessage = True
        def test_gevent_spawn(self):
            timer = time.time()
            d1, d2, d3 = 1, 2, 3
            t1 = gevent.spawn(f, d1)
            t2 = gevent.spawn(f, d2)
            t3 = gevent.spawn(f, d3)
            gevent.joinall([t1, t2, t3])
            cost = time.time() - timer
            self.assertAlmostEqual(cost, max(d1, d2, d3), delta=1.0,
                                   msg='gevent spawn not working as expected')
    

参考

  • pytest-django文档
  • pytest文档

使用/manage.py测试--迁移文件中没有更改时的keepdb

数据库初始化确实花费了太长时间。。。

我有一个项目,有大约相同数量的模型/表(大约77个),大约350个测试,总共需要1分钟来运行所有内容。在一台分配了2个cpu和2GB内存的流浪机器中进行设计。此外,我使用py.test和pytest-xlist插件并行运行多个测试。

您可以做的另一件事是告诉django重用测试数据库,并且只有在模式发生更改时才重新创建它。此外,您还可以使用SQLite,以便测试将使用内存中的数据库。这两种方法都在这里进行了解释:https://docs.djangoproject.com/en/dev/topics/testing/overview/#the-测试数据库

EDIT:如果上面的选项都不起作用,还有一个选项是让您的单元测试从django SimpleTestCase继承,或者使用一个自定义测试运行程序,该运行程序不会创建数据库,如这里的答案所述:django单元测试没有数据库。

然后,您可以使用这样的库来模拟django对数据库的调用(我写了一个令人钦佩的库):https://github.com/stphivos/django-mock-queries

这样,在将代码合并到某个稳定的dev/master分支(而不是生产分支)之前,您可以在本地快速运行单元测试,并让CI服务器担心运行需要数据库的集成测试。

如果您使用的是Postgres,请使用Postgres模板存储一个空数据库的副本,并应用所有迁移。然后将Django配置为在创建测试数据库时使用该模板。

一种方法:

  1. 使用--keepdb参数运行Django的测试命令
  2. 在Postgres上,使用查询重命名创建的测试数据库

alter database "test_your_db_name" rename to "test_your_db_name_template";

  1. 在Postgres上,使用查询使数据库成为模板

alter database "test_your_db_name_template" IS_TEMPLATE = true

  1. 使用以下内容调整测试设置:
DATABASES["default"]["TEST"] = {}
DATABASES["default"]["TEST"]["TEMPLATE"] = "test_your_db_name_template"
  1. 在不使用--keepdb参数的情况下再次运行Django测试。由于迁移已经应用于模板数据库,因此它们将不必再次运行,因此测试将立即开始

另请参阅文档:

  • Django TEMPLATE数据库设置

我也遇到了问题,我所做的一个解决方案是将Djang.TestCase子类化-->创建Djang.TestCase 的子类

并重写方法,如:

@类方法def_database_support_transactions(cls):return True

后端数据库是apache cassandra。。

最新更新