python覆盖模块可以有条件地忽略单元测试中的行吗?

Posted

技术标签:

【中文标题】python覆盖模块可以有条件地忽略单元测试中的行吗?【英文标题】:Can python coverage module conditionally ignore lines in a unit test? 【发布时间】:2016-06-01 11:53:39 【问题描述】:

使用鼻子测试和覆盖率模块,我想要代码的覆盖率报告以反映正在测试的版本。考虑这段代码:

import sys
if sys.version_info < (3,3):
    print('older version of python')

当我在 python 3.5 版中测试时,print() 显示为未经测试。我希望覆盖忽略该行,但仅在我使用 python 3.3+ 版本进行测试时

只有当sys.version_info 不小于(3,3) 时,有没有办法在print() 语句上执行类似# pragma: no cover 的操作?实际上,我想做这样的事情:

import sys
if sys.version_info < (3,3):
    print('older version of python') # pragma: [py26,py27,py32] no cover

【问题讨论】:

既然你知道你对那个部分的覆盖率不感兴趣,为什么覆盖率分析忽略它很重要?您是否尝试在覆盖率下降时实现一些自动报告,或者潜在的问题是什么? 覆盖率报告只显示遗漏的行号,每次我想确保覆盖率足够时,我必须记住在哪些测试运行中应该忽略哪些行。这似乎容易出错(也许不会?)而且有点费时。 您可以将多个覆盖运行合并在一起,而不是忽略它们?使用 Python 2 运行,然后使用 Python 3 并合并覆盖数据? @Schwern 这是一个好主意,我认为它会成为一个很棒的插件/对 tox 的补充。它为这个问题提供了一个有效的解决方案,但没有解决我对 windows 和 linux 有不同分支的(未说出的)情况。我应该创建一个单独的问题还是编辑这个问题? 同样的基本问题。合并多次运行仍然有效。另一种解决方案是将兼容性问题封装到子类中,以便您的异常更易于管理。 【参考方案1】:

另一种选择是为不同版本的 Python 使用不同的 .coveragerc 文件,并为不同的版本设置不同的 exclude_lines 正则表达式。

我看到有些人使用不同的评论字符串,例如 # no cover 3.x# no cover 2.x

但请记住,您根本不必使用注释编译指示。正则表达式应用于整行。例如,如果您对条件使用简短的符号,例如:

if PY2:
    blah_py2_stuff_blah()

那么你的 Python 3 的 .coveragerc 文件可能有:

[report]
exclude_lines =
    # pragma: no cover
    if PY2:

那么if PY2: 行将被排除在外,无需您进行任何额外的 cmets 或努力。

【讨论】:

这很棒。我喜欢这意味着源代码不会被编译指示弄得杂乱无章。我最终基于平台和 python 版本在setup.py 中创建了.coveragerc 文件。与 tox 甚至在不同的平台(windows/linux/mac)上都像魅力一样工作。 @Johann 你能分享一个例子,说明你是如何从setup.py 构建你的.coveragerc 的吗?【参考方案2】:

正如您在 cmets 中解释的那样,您担心的是覆盖率报告只会显示行号,您希望避免一次又一次地重新检查这些内容。

另一方面,我不太赞成使用 cmets 使代码混乱以使一个或另一个工具满意:对我来说,这一切都会降低可读性。因此,我想提出另一种方法,它可以避免代码混乱,但仍然可以减轻您一直进行重新检查的负担。

这个想法是,创建覆盖情况的基线,您可以根据该基线比较未来的覆盖分析结果。例如,coverage.py 中的覆盖率报告如下(引用自http://coverage.readthedocs.org/en/coverage-4.0.3/index.html):

Name                      Stmts   Miss  Cover   Missing
-------------------------------------------------------
my_program.py                20      4    80%   33-35, 39
my_other_module.py           56      6    89%   17-23
-------------------------------------------------------
TOTAL                        76     10    87%

此输出可用作“基线”的基础:粗略的想法(改进见下文)是,您将此输出存储为“已接受”覆盖情况,并将其与未来的覆盖报告进行比较。不幸的是,每当行号发生变化时,您都会在区分报告时看到差异。为了避免这种情况,可以改进这个基本思想:

借助简单的脚本,您可以转换报告,以显示相应行的内容,而不是行号。例如,基于上述代码示例的假设报告可能如下所示:

Name                      Stmts   Miss  Cover   Missing
-------------------------------------------------------
my_program.py                20      1     5%   3

从此报告中,您可以为 >= 3.3 的 Python 版本创建以下“覆盖率基线”,例如在文件 coverage-baseline-33andabove.txt 中:

my_program.py:
-    print('older version of python')

即使您在文件顶部添加其他导入行,该基线看起来也一样。将为您确定覆盖范围的其他 python 版本创建更多基线文件。

进一步可能的改进可能是将行组分开,例如:

my_program.py:
*
-    print('older version of python')
*
-    cleanup()
-    assert False
my_program2.py:
*
-    print('older version of python')

您只会在未涵盖的代码更改(添加、删除、修改、移动)以及文件名更改时看到差异。然后,差异的出现将要求您存储新的“覆盖基线”,或者添加更多测试,直到再次达到原始基线内容。

【讨论】:

【参考方案3】:

我为coverage 库编写了一个插件。它可用于根据不同的用户定义标准有条件地从覆盖范围中排除块和线。

它支持:

sys_version_infosys.version_info 相同 os_environos.environ 相同 is_installed 是我们的自定义函数,它尝试导入传递的字符串,返回 bool 值 package_version 是我们的自定义函数,它尝试从 pkg_resources 获取包版本并返回其解析版本

这是一个例子:

try:  # pragma: has-django
    import django
except ImportError:  # pragma: has-no-django
    django = None

def run_if_django_is_installed():
    if django is not None:  # pragma: has-django
        ...

此示例将需要添加以下行:

[coverage:run]
# Here we specify plugins for coverage to be used:
plugins =
  coverage_conditional_plugin

[coverage:coverage_conditional_plugin]
rules =
  "is_installed('django')": has-django
  "not is_installed('django')": has-no-django

现在,标有# pragma: has-django 的行在未安装django 时将被忽略,但在安装时被覆盖。而相反的情况适用于has-no-django pragma。

来源:https://github.com/wemake-services/coverage-conditional-plugin 简介:https://sobolevn.me/2020/02/conditional-coverage

【讨论】:

哇,太棒了!你有计划让它与 Python 3.9 兼容吗? 请为此打开一个问题。我完全想要这个,但可以忘记它!

以上是关于python覆盖模块可以有条件地忽略单元测试中的行吗?的主要内容,如果未能解决你的问题,请参考以下文章

有条件地显示具有匹配值的单元格的行中的值

多个模块的 Python 单元测试覆盖率

单元测试总结

有条件地忽略 JUnit 4 中的测试

Python:如何在单元(鼻子)测试期间忽略装饰器?

如何忽略 Next.js 组件中导致单元测试中的解析错误的 CSS 模块导入