Как правильно проверить покрытие с помощью 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

В: Почему тела функций (или классов) отображаются как выполненные, а строки определения — нет?

Это происходит потому, что покрытие начинается после определения функций. Линии определения выполняются без измерения покрытия, затем запускается покрытие, затем вызывается функция. Это означает, что тело измеряется, а определение самой функции — нет.

Чтобы исправить это, начните охват раньше. Если вы используете командную строку для запуска вашей программы с покрытием, то вся ваша программа будет отслеживаться. Если вы используете API, перед импортом модулей, определяющих ваши функции, необходимо вызвать покрытие.start().

Вопрос в том, могу ли я правильно запускать отчеты о покрытии с помощью команды управления Django? Или мне нужно обойти, чтобы избежать ситуации, когда покрытие начинается после выполнения «недостающих» строк?


person kaptainlange    schedule 10.07.2014    source источник


Ответы (5)


На данный момент невозможно точно запустить покрытие вместе с django-nose (из-за того, как Django 1.7 загружает модели). Итак, чтобы получить статистику покрытия, вам нужно использоватьcoverage.py непосредственно из командной строки, например:

$ coverage run --branch --source=app1,app2 ./manage.py test
$ coverage report
$ coverage html -d coverage-report

Вы можете поместить настройкиcoverage.py в файл .coveragerc в корне проекта (тот же каталог, что и manage.py).

Об этой проблеме сообщается на странице django-nose GitHub: https://github.com/django-nose/django-nose/issues/180, чтобы сопровождающие знали о проблеме, вы можете сообщить им, что вы также столкнулись с этой проблемой.

ОБНОВЛЕНИЕ

eliangcs указал (проблемы с django-nose на 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-nose, но попробовал py.test и никогда не оглядывался назад.

person iyn    schedule 15.04.2015
comment
Это самое удобное решение (хотя и хакерское) и отлично и прозрачно работает с django-nose. - person danius; 03.09.2015
comment
Спасибо за совет по pytest - person Nithin; 27.06.2019

Как говорится в документации, «используйте командную строку для запуска вашей программы с покрытием»:

coverage run --branch --source=notify ./manage.py test
person Ned Batchelder    schedule 10.07.2014
comment
Спасибо Нед. Мой вопрос был конкретно о том, можно ли запустить управление и получить правильные результаты. Буду ли я прав, если предположу, что нет, никак? - person kaptainlange; 11.07.2014
comment
Я не знаю, почему --with-coverage работает у одних людей, а у других нет. - person Ned Batchelder; 11.07.2014

Я провел какое-то время с этой проблемой, и даже с учетом полученных ответов они не были достаточно подробными, чтобы полностью объяснить, что я испытал. Вот что у меня сейчас хорошо работает, согласно ответу 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://stackoverflow.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://stackoverflow.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

И теперь я вижу, что model.py, admins.py и apps.py покрываются.

Я запускаю свои интеграционные тесты так (без покрытия):

./manage.py test integration_tests/itest_*  --noinput

Я также могу запустить определенный набор тестов, например:

./manage.py test --noinput --verbose client_app/tests.py

Вы также можете изменить NOSE_ARGS по своему желанию или полностью исключить его, если собираетесь использовать флаги каждый раз в командной строке. Ваше здоровье!

person radtek    schedule 06.02.2019

Мне удалось заставить это работать, включая

import coverage

поверх моего файла manage.py (вместо этого я использую Flask, но у меня такая же проблема)

Моя проблема в том, что он работает с консоли, но Дженкинс не знает об этом и продолжает говорить, что этот импорт не тестируется...

Любая идея?

person F.D.F.    schedule 24.10.2014

У меня была такая же проблема с использованием удаленного интерпретатора на виртуальной машине через конфигурацию ssh. Решение состояло в том, чтобы установить мой каталог тестов и ВСЕ его родительские каталоги в «Сопоставление путей» раздела «Среда» «Выполнить»> «Редактировать конфигурации ...».

person tebanep    schedule 28.01.2015