Как правильно протестировать покрытие с помощью 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. Решение состояло в том, чтобы установить мой тестовый каталог и ВСЕ его родительские каталоги в "Сопоставлениях пути" раздела "Среда" в "Выполнить" > "Редактировать конфигурации...".

Другие вопросы по тегам