50

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

运行测试时,从开始到实际开始测试之间有 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:18 分钟

我有大约 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-nosewithdjango-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

我压制了我所有的迁移,但仍然很慢。

4

4 回答 4

49

解决我的问题的最终解决方案是强制 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 秒。

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

于 2016-05-11T00:14:26.253 回答
37

概括

使用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一次以强制重新创建测试数据库

  • 如果您需要在测试期间启用gevent 猴子补丁,您可以在项目根目录中创建一个名为的文件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猴子补丁是否成功:

    # -*- 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')
    

参考

于 2016-09-30T03:57:55.233 回答
24

当迁移文件没有变化时使用./manage.py test --keepdb

于 2017-09-18T13:43:39.093 回答
6

数据库初始化确实需要太长时间......

我有一个项目,它的模型/表数量大致相同(大约 77 个),大约有 350 个测试,总共需要 1 分钟来运行所有内容。在分配了 2 个 CPU 和 2GB 内存的 vagrant 机器上进行开发。我还使用 py.test 和 pytest-xdist 插件来并行运行多个测试。

您可以做的另一件事是告诉 django 重用测试数据库,并且仅在架构更改时重新创建它。您也可以使用 SQLite,以便测试使用内存数据库。两种方法都在这里解释: https ://docs.djangoproject.com/en/dev/topics/testing/overview/#the-test-database

编辑:如果上述选项均无效,另一种选择是让您的单元测试从 django SimpleTestCase 继承或使用不创建数据库的自定义测试运行器,如此处的答案所述:django unit tests without a db .

然后,您可以使用像这样的库来模拟对数据库的 django 调用(我承认这是我写的):https ://github.com/stphivos/django-mock-queries

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

于 2016-04-07T22:20:16.290 回答