【问题标题】:How to test coverage properly with Django + Nose如何使用 Django + Nose 正确测试覆盖率
【发布时间】:2014-07-10 03:50:14
【问题描述】:

目前有一个项目配置为通过 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 的管理命令正确运行覆盖率报告吗?还是我必须绕过管理以避免在执行“缺失”行之后开始覆盖的情况?

【问题讨论】:

    标签: python django code-coverage django-nose


    【解决方案1】:

    目前不可能与 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 指出(GiHub 上的 django-nose 问题),解决方法是修改您的 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()
    

    它有效,但它是相当“hacky”的方法。

    更新 2

    我推荐所有使用nose的人看看py.test (http://pytest.org/),它是一个非常好的Python测试工具,它与Django集成得很好,有很多插件等等。我正在使用 django-nose,但尝试了 py.test 并且从未回头。

    【讨论】:

    • 这是最方便的解决方案(虽然 hacky),并且与 django-nose 完美透明地配合使用
    • 感谢 pytest 提示
    【解决方案2】:

    正如文档所说,“使用命令行运行覆盖范围的程序”:

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

    【讨论】:

    • 谢谢内德。我的问题特别是关于是否有可能通过管理并获得正确的结果。我假设不,没有办法是正确的吗?
    • 我不知道为什么 --with-coverage 对某些人有效,对其他人无效。
    【解决方案3】:

    我花了一些时间来解决这个问题,即使给出了答案,它们也不够详细,无法完全解释我的经历。根据 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
    

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

    我像这样运行我的集成测试(没有覆盖):

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

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

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

    您也可以根据需要修改NOSE_ARGS,或者如果您打算每次都在命令行上使用这些标志,则将其完全省略。干杯!

    【讨论】:

      【解决方案4】:

      我已经设法让这个工作,包括一个

      import coverage
      

      在我的 manage.py 文件之上(我正在使用 Flask,但遇到了同样的问题)

      我的问题是它可以在控制台上运行,但 Jenkins 并没有意识到这一点,并一直说这些导入不在测试范围内......

      有什么想法吗?

      【讨论】:

        【解决方案5】:

        我通过 ssh 配置在虚拟机中使用远程解释器时遇到了同样的问题。解决方案是在“运行”>“编辑配置...”的“环境”部分的“路径映射”中设置我的测试目录和 ALL 其父目录。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-05-25
          • 2012-03-25
          • 1970-01-01
          • 2016-11-11
          • 2014-02-26
          • 2012-11-11
          • 2013-05-07
          • 2017-02-06
          相关资源
          最近更新 更多