Как правильно протестировать покрытие с помощью 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-интерфейс, вам нужно вызывать cover.start() перед импортом модулей, которые определяют ваши функции.
Вопрос в том, могу ли я правильно запустить отчеты о покрытии с помощью команды управления Django? Или мне нужно обходить управление, чтобы избежать ситуации, когда покрытие запускается после выполнения "пропущенных" строк?
4 ответа
В настоящее время невозможно точно запустить покрытие вместе с носом django (из-за того, как Django 1.7 загружает модели). Таким образом, чтобы получить статистику покрытия, вам нужно использовать cover.py непосредственно из командной строки, например:
$ coverage run --branch --source=app1,app2 ./manage.py test
$ coverage report
$ coverage html -d coverage-report
Вы можете поместить настройки cover.py в файл.coveragerc в корне проекта (тот же каталог, что и manage.py).
Об этой проблеме сообщается на странице GitHub django-носа: https://github.com/django-nose/django-nose/issues/180 поэтому сопровождающие знают о проблеме, вы можете сообщить им, что вы тоже испытываете эту проблему,
ОБНОВИТЬ
eliangcs отметил (проблемы с django-носом на GiHub), что woraround - это модификация вашего 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-nose, но попробовал py.test и никогда не оглядывался назад.
Как говорится в документации, "используйте командную строку для запуска вашей программы с покрытием":
coverage run --branch --source=notify ./manage.py test
Я провел некоторое время с этой проблемой, и даже с учетом ответов, они не были достаточно подробными, чтобы полностью объяснить, что я испытывал. Вот то, что хорошо работает для меня сейчас, согласно ответу от 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's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
# See https://stackru.com/questions/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
Я бросил, используя упаковку и with-coverage
как это уже обрабатывается в manage.py
сейчас. Вот мои настройки с некоторыми пояснениями:
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
# These are global options, trim as needed
# See https://stackru.com/questions/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
по вашему желанию или не включайте его полностью, если вы собираетесь каждый раз использовать флаги в командной строке. Ура!
Мне удалось заставить это работать, включая
import coverage
поверх моего файла manage.py (вместо этого я использую Flask, но у меня та же проблема)
Моя проблема заключается в том, что он работает с консоли, но Дженкинс не знает об этом и продолжает говорить, что этот импорт вне испытаний...
Любая идея?
У меня была такая же проблема с использованием удаленного переводчика на виртуальной машине через конфигурацию ssh. Решение состояло в том, чтобы установить мой тестовый каталог и ВСЕ его родительские каталоги в "Сопоставлениях пути" раздела "Среда" в "Выполнить" > "Редактировать конфигурации...".