Django 单元测试需要很长时间才能创建测试数据库
Django Unit Testing taking a very long time to create test database
一段时间以来,我的单元测试花费的时间比预期的要长。我已经尝试调试它几次但没有成功,因为延迟甚至在我的测试开始之前 运行。这影响了我做任何远程接近测试驱动开发的能力(也许我的期望太高了),所以我想看看我是否可以一劳永逸地解决这个问题。
当 运行 测试时,在测试开始和实际开始之间有 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 进行单元测试,并实施了一些建议的改进。我不能 post 我的整个设置文件,但很乐意添加所需的任何信息。
我用 运行ner
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
我压缩了所有迁移,但仍然很慢。
数据库初始化确实耗时太长...
我有一个项目 models/tables(大约 77),大约有 350 个测试,总计 运行 需要 1 分钟。在分配了 2 个 CPU 和 2GB 内存的流浪机器中进行开发。此外,我将 py.test 与 pytest-xdist 插件一起用于 运行 并行进行多个测试。
您可以做的另一件事是告诉 django 重用测试数据库,并且仅在架构更改时才重新创建它。您也可以使用 SQLite,以便测试将使用内存数据库。此处解释了两种方法:
https://docs.djangoproject.com/en/dev/topics/testing/overview/#the-test-database
编辑:如果none以上选项有效,另一种选择是让您的单元测试继承自django SimpleTestCase或使用自定义测试运行ner 不会创建数据库,如此处的答案所述:django unit tests without a db.
然后你可以使用像这样的库(承认我写的)来模拟 django 对数据库的调用:https://github.com/stphivos/django-mock-queries
通过这种方式,您可以 运行 在本地快速进行单元测试,并让您的 CI 服务器担心 运行 需要数据库的集成测试,然后再将您的代码合并到某个稳定的dev/master 不是生产分支。
解决我的问题的最终解决方案是强制 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
的文件,并在其中指定 default command line options and/or Django settings。
# content of pytest.ini
[pytest]
addopts = --nomigrations
DJANGO_SETTINGS_MODULE = yourproject.settings
现在您可以简单地 运行 使用 pytest
进行测试并节省您的输入时间。
您可以通过将 --reuse-db
添加到默认命令行选项来进一步加快后续测试。
[pytest]
addopts = --nomigrations --reuse-db
但是,一旦你的数据库模型改变了,你必须运行pytest --create-db
一次到force re-creation of the test database。
如果在测试时需要启用gevent monkey patching,可以在项目根目录下创建一个名为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')
参考资料
迁移文件没有变化时使用./manage.py test --keepdb
一段时间以来,我的单元测试花费的时间比预期的要长。我已经尝试调试它几次但没有成功,因为延迟甚至在我的测试开始之前 运行。这影响了我做任何远程接近测试驱动开发的能力(也许我的期望太高了),所以我想看看我是否可以一劳永逸地解决这个问题。
当 运行 测试时,在测试开始和实际开始之间有 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 进行单元测试,并实施了一些建议的改进。我不能 post 我的整个设置文件,但很乐意添加所需的任何信息。
我用 运行ner
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
我压缩了所有迁移,但仍然很慢。
数据库初始化确实耗时太长...
我有一个项目 models/tables(大约 77),大约有 350 个测试,总计 运行 需要 1 分钟。在分配了 2 个 CPU 和 2GB 内存的流浪机器中进行开发。此外,我将 py.test 与 pytest-xdist 插件一起用于 运行 并行进行多个测试。
您可以做的另一件事是告诉 django 重用测试数据库,并且仅在架构更改时才重新创建它。您也可以使用 SQLite,以便测试将使用内存数据库。此处解释了两种方法: https://docs.djangoproject.com/en/dev/topics/testing/overview/#the-test-database
编辑:如果none以上选项有效,另一种选择是让您的单元测试继承自django SimpleTestCase或使用自定义测试运行ner 不会创建数据库,如此处的答案所述:django unit tests without a db.
然后你可以使用像这样的库(承认我写的)来模拟 django 对数据库的调用:https://github.com/stphivos/django-mock-queries
通过这种方式,您可以 运行 在本地快速进行单元测试,并让您的 CI 服务器担心 运行 需要数据库的集成测试,然后再将您的代码合并到某个稳定的dev/master 不是生产分支。
解决我的问题的最终解决方案是强制 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
的文件,并在其中指定 default command line options and/or Django settings。# content of pytest.ini [pytest] addopts = --nomigrations DJANGO_SETTINGS_MODULE = yourproject.settings
现在您可以简单地 运行 使用
pytest
进行测试并节省您的输入时间。您可以通过将
--reuse-db
添加到默认命令行选项来进一步加快后续测试。[pytest] addopts = --nomigrations --reuse-db
但是,一旦你的数据库模型改变了,你必须运行
pytest --create-db
一次到force re-creation of the test database。如果在测试时需要启用gevent monkey patching,可以在项目根目录下创建一个名为
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')
参考资料
迁移文件没有变化时使用./manage.py test --keepdb