一段时间以来,我的单元测试花费的时间比预期的要长。我试过几次调试它,但都没有成功,因为延迟是在我的测试开始运行之前。这影响了我做任何接近测试驱动开发的远程工作的能力(也许我的期望值太高了),所以我想看看我是否能一劳永逸地解决这个问题。
当运行测试时,测试的开始和实际开始之间有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-nose
与django-nose-exclude
一起使用
我读了很多关于如何加快测试速度的文章,但没有找到任何关于如何优化或避免数据库初始化的线索。我看到了关于尽量不使用数据库进行测试的建议,但我无法或不知道如何完全避免这种情况。
如果
- 这是正常的,也是意料之中的
- 没有预料到(希望能解决或引导该怎么做)
同样,我不需要关于如何加快测试本身的帮助,而是需要初始化(或开销)的帮助。我希望上面的例子用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
!
操作
pip install pytest-django
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配置为在创建测试数据库时使用该模板。
一种方法:
- 使用
--keepdb
参数运行Django的测试命令 - 在Postgres上,使用查询重命名创建的测试数据库
alter database "test_your_db_name" rename to "test_your_db_name_template";
- 在Postgres上,使用查询使数据库成为模板
alter database "test_your_db_name_template" IS_TEMPLATE = true
- 使用以下内容调整测试设置:
DATABASES["default"]["TEST"] = {}
DATABASES["default"]["TEST"]["TEMPLATE"] = "test_your_db_name_template"
- 在不使用
--keepdb
参数的情况下再次运行Django测试。由于迁移已经应用于模板数据库,因此它们将不必再次运行,因此测试将立即开始
另请参阅文档:
- Django TEMPLATE数据库设置
我也遇到了问题,我所做的一个解决方案是将Djang.TestCase子类化-->创建Djang.TestCase 的子类
并重写方法,如:
@类方法def_database_support_transactions(cls):return True
后端数据库是apache cassandra。。