Как правильно проверить покрытие с помощью Django + Nose
В настоящее время проект настроен для запуска покрытия с помощью команды управления Django следующим образом:
./manage.py test --with-coverage --cover-package=notify --cover-branches --cover-inclusive --cover-erase
В результате появляется отчет следующего вида:
Name Stmts Miss Branch BrMiss Cover Missing
--------------------------------------------------------------------------
notify.decorators 4 1 0 0 75% 4
notify.handlers 6 1 2 0 88% 11
notify.notification_types 46 39 2 0 19% 8-55, 59, 62, 66
notify.notifications 51 51 0 0 0% 11-141
--------------------------------------------------------------------------
TOTAL 107 92 4 0 17%
Однако проблема с этим отчетом. Это неправильно. Охват означает отсутствие пропусков линий, несмотря на то, что они действительно покрываются испытаниями. Например, если я запускаю тесты через nosetests
вместо команды управления django, я получаю следующий правильный отчет:
Name Stmts Miss Branch BrMiss Cover Missing
-----------------------------------------------------------------------------
notify.decorators 4 0 0 0 100%
notify.handlers 6 0 2 0 100%
notify.notification_types 46 0 2 0 100%
notify.notifications 51 25 0 0 51% 13, 18, 23, 28, 33, 38, 43, 48, 53, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 116, 121, 126, 131, 136, 141
-----------------------------------------------------------------------------
TOTAL 107 25 4 0 77%
Google привел меня на часто задаваемые вопросы о веб-сайте покрытия, http://nedbatchelder.com/code/coverage/faq.html
В: Почему тела функций (или классов) отображаются как выполненные, но строки def не?
Это происходит потому, что покрытие запускается после того, как функции определены. Строки определения выполняются без измерения покрытия, затем начинается освещение, затем вызывается функция. Это означает, что тело измеряется, но определение самой функции не является.
Чтобы исправить это, начните покрытие раньше. Если вы используете командную строку для запуска вашей программы с охватом, ваша программа будет контролироваться. Если вы используете API, перед импортом модулей, которые определяют ваши функции, вы должны вызвать функцию coverage.start().
Вопрос в том, могу ли я правильно запускать отчеты о покрытии с помощью команды управления Django? Или мне нужно обойти управление, чтобы избежать ситуации, когда покрытие запускается после выполнения "отсутствующих" строк?
Ответы
Ответ 1
В настоящий момент невозможно точно выполнить покрытие наряду с джанго-носом (из-за того, как модели Django 1.7 загружают модели). Поэтому, чтобы получить статистику покрытия, вам нужно использовать cover.py непосредственно из командной строки, например:
$ coverage run --branch --source=app1,app2 ./manage.py test
$ coverage report
$ coverage html -d coverage-report
Вы можете установить параметры покрытия .py в файл .coveragerc в корне проекта (тот же каталог, что и manage.py).
Эта проблема сообщается на странице django-nose GitHub: https://github.com/django-nose/django-nose/issues/180, чтобы сопровождающие знали о проблеме, вы можете сообщить им, что вы Эта проблема также возникает.
UPDATE
eliangcs указала (проблемы с Django-носом на GiHub), что важно изменить ваш manage.py
:
import os
import sys
if __name__ == "__main__":
# ...
from django.core.management import execute_from_command_line
is_testing = 'test' in sys.argv
if is_testing:
import coverage
cov = coverage.coverage(source=['package1', 'package2'], omit=['*/tests/*'])
cov.erase()
cov.start()
execute_from_command_line(sys.argv)
if is_testing:
cov.stop()
cov.save()
cov.report()
Это работает, но это скорее "хакерский" подход.
ОБНОВЛЕНИЕ 2
Я рекомендую всем, кто использует нос, чтобы посмотреть на py.test(http://pytest.org/), что является действительно хорошим инструментом тестирования Python, он хорошо интегрируется с Django, имеет множество плагинов и многое другое. Я использовал django-нос, но попробовал py.test и никогда не оглядывался назад.
Ответ 2
Как говорят документы, "используйте командную строку для запуска вашей программы с покрытием":
coverage run --branch --source=notify ./manage.py test
Ответ 3
Мне удалось получить эту работу, включая
import coverage
поверх моего файла manage.py(вместо этого я использую Flask, но имею ту же проблему)
Моя проблема в том, что она работает с консоли, но Дженкинс не знает об этом и продолжает говорить о том, что эти импортные данные выходят за рамки тестов...
Любая идея?
Ответ 4
У меня была такая же проблема с использованием удаленного интерпретатора в виртуальной машине через конфигурацию ssh. Решение состояло в том, чтобы установить каталог моих тестов и ВСЕ его родительские каталоги в "Отображениях пути" раздела "Окружающая среда" в разделе "Выполнить" > "Редактировать конфигурации...".
Ответ 5
Я провел некоторое время с этой проблемой, и даже с учетом ответов, они не были достаточно подробными, чтобы полностью объяснить, что я испытывал. Вот то, что хорошо работает для меня сейчас, согласно ответу от iyn с несколькими необходимыми изменениями. Мой manage.py выглядит так:
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
# See https://stackoverflow.com/info/24668174/how-to-test-coverage-properly-with-django-nose
is_coverage_testing = 'test' in sys.argv and '--with-coverage' in sys.argv
# Drop dupe with coverage arg
if '--with-coverage' in sys.argv:
sys.argv.remove('--with-coverage')
if is_coverage_testing:
import coverage
cov = coverage.coverage(source=['client_app', 'config_app', 'list_app', 'core_app', 'feed_app',
'content_app', 'lib',
'job_app', 'license_app', 'search_app', 'weather_app'],
omit=['*/integration_tests/*'])
cov.erase()
cov.start()
execute_from_command_line(sys.argv)
if is_coverage_testing:
cov.stop()
cov.save()
cov.report()
Как видно из вышеизложенного, я включил все свои приложения для тестирования и исключил, где я храню интеграционные тесты.
Мой settings.py
Я удалил, используя пакет cover и with-coverage
cover, поскольку это уже обрабатывается в manage.py
сейчас. Вот мои настройки с некоторыми пояснениями:
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
# These are global options, trim as needed
# See https://stackoverflow.com/info/24668174/how-to-test-coverage-properly-with-django-nose
NOSE_ARGS = [
# '--cover-package=client_app', # included in manage.py (hack to include all app testing)
# '--cover-package=config_app',
# '--cover-package=content_app',
# '--cover-package=job_app',
# '--cover-package=lib',
# '--cover-package=license_app',
# '--cover-package=list_app',
# '--cover-package=search_app',
# '--cover-package=core_app',
# '--cover-package=weather_app',
# '--cover-package=feed_app',
'--logging-level=INFO',
'--cover-erase',
# '--with-coverage', # Included in manage.py (hack), do not use here or will create multiple reports
# '--cover-branches', # Lowers coverage
'--cover-html', # generate HTML coverage report
'--cover-min-percentage=59',
# '--cover-inclusive', # can't get coverage results on most files without this... This breaks django tests.
]
Я запускаю свои основные тесты так (с покрытием):
./manage.py test --noinput --verbose --with-coverage
И теперь я вижу, как покрываются models.py, admins.py и apps.py.
Я запускаю свои интеграционные тесты так (без покрытия):
./manage.py test integration_tests/itest_* --noinput
Я также могу запустить определенный набор тестов, например, так:
./manage.py test --noinput --verbose client_app/tests.py
Вы также можете изменить NOSE_ARGS
желанию или оставить его полностью, если вы собираетесь использовать флаги каждый раз в командной строке. Ура!