如何在Django + Nose中正确测试覆盖率

17 投票
5 回答
10105 浏览
提问于 2025-04-18 12:44

目前我有一个项目,配置了通过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%   

我在谷歌上找到了覆盖率网站的常见问题解答,链接是 http://nedbatchelder.com/code/coverage/faq.html

问:为什么函数(或类)的主体显示为已执行,但定义行却没有?

这是因为覆盖率是在函数定义之后才开始的。定义行在没有覆盖率测量的情况下被执行,然后才开始覆盖率测量,接着才调用函数。这意味着函数的主体被测量了,但函数本身的定义却没有。

要解决这个问题,需要更早地启动覆盖率。如果你使用命令行来运行程序并监控覆盖率,那么整个程序都会被监控。如果你使用API,则需要在导入定义函数的模块之前调用coverage.start()。

我的问题是,我能否通过Django的管理命令正确运行覆盖率报告?还是说我必须绕过管理命令,以避免在“缺失”的行被执行后才启动覆盖率的情况?

5 个回答

-1

我之前也遇到过类似的问题,使用远程解释器在虚拟机里通过ssh配置。解决办法是把我的测试文件夹和所有上级文件夹都设置到“运行” > “编辑配置...”中的“环境”部分的“路径映射”里。

-1

我已经成功让这个工作起来,包括在我的 manage.py 文件上方加了一些东西。

import coverage

我其实是在用 Flask,但遇到的这个问题和我用的工具是一样的。

我的问题是,这在控制台里能正常工作,但 Jenkins 却不知道这一点,老是说那些导入的东西不在测试范围内……

有没有什么想法?

0

我花了一些时间在这个问题上,尽管有一些答案,但它们并没有详细解释我遇到的情况。现在对我来说,以下方法效果很好,参考了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文件中,我不再使用cover包和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

现在我可以看到models.py、admins.py和apps.py都被覆盖了。

我这样运行我的集成测试(不带覆盖率):

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

我也可以这样运行一组特定的测试:

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

你可以根据需要修改NOSE_ARGS,或者如果你打算每次在命令行上使用标志,可以完全不写它。祝好运!

6

根据文档的说明,“使用命令行来运行你的程序并查看覆盖率”:

coverage run --branch --source=notify ./manage.py test
17

目前,无法准确地在使用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在GitHub上提到,解决这个问题的一个方法是修改你的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

我建议所有使用nose的人看看py.test(http://pytest.org/),这是一个非常好的Python测试工具,和Django兼容得很好,还有很多插件等等。我之前用的是django-nose,但试过py.test后就再也没有回头过。

撰写回答