【问题标题】:How to test a Python CLI program with click, coverage.py, and Tox?如何使用 click、coverage.py 和 Tox 测试 Python CLI 程序?
【发布时间】:2018-10-09 04:11:16
【问题描述】:

我正在使用 click 开发一个 CLI 程序,我想开始使用 coverage.py 添加一些带有代码覆盖率分析的测试。

我认为实现测试的一个好方法是使用子进程运行 CLI 本身。但是,coverage.py 报告零代码覆盖率,可能是因为子进程生成的 Python 实例没有 coverage.py 检测。

我发现 this link 说我可以在我的 PYTHONPATH 中删除一个 sitecustomize.py 文件以始终强制 Python 开始覆盖率测量,但我正在使用 Tox 创建一个 venv 并运行测试。我找不到任何处理此问题的 Tox 设置。

我发现 this answer 说我应该通过 coverage run 运行我的 CLI,但它看起来只有在给出 Python 脚本的路径时才有效,并且我正在尝试通过入口点运行我的 CLI在setup.py 中定义。即我必须将测试代码中的所有命令行从myprogram 更改为coverage run myprogram/cli/cli.py。我宁愿不这样做,因为这不是我期望用户运行程序的方式。

所以看起来这两个选项是:

  1. 想办法让sitecustomize.py 在Tox 环境中工作,或者

  2. 测试代码中的所有命令行都使用脚本路径而不是入口点(从长远来看可能更容易,但会使测试更脆弱且更难理解)。想知道我是否还缺少其他任何东西。

【问题讨论】:

  • FWIW 当我编写类似的程序时,我总是编写测试,其中我的程序与测试代码在同一进程中运行。测试只是按预期设置了os.environsys.argv,然后直接调用myprogram.main()。这种方法适用于toxcoverage 和我使用过的其他工具。
  • 这很好,我没想过要更改sys.argv。如果需要,您可以将其添加为答案。 This answer 说你应该用 unittest.mock 模拟 sys.argv,但这感觉有点矫枉过正,不知道为什么......
  • 另外,看起来click.testing.CliRunner 应该做正确的事情来在单元测试中调用 CLI。我没有找到,因为我正在寻找看起来像 argparse.ArgumentParser:parse_args() 的东西。

标签: python code-coverage coverage.py python-click


【解决方案1】:

CliRunner 和一个合适的单元测试框架是要走的路。这是PyTest 的示例设置,它使用 PyTest Coverage plugin

test_click.py

from click.testing import CliRunner

from click_prog import hello


def test_hello_world():

    runner = CliRunner()
    result = runner.invoke(hello, ['--opt', 'An Option', 'An Arg'])
    assert result.exit_code == 0
    assert result.output == 'Opt: An Option  Arg: An Arg\n'

    result = runner.invoke(hello, ['An Arg'])
    assert result.exit_code == 0
    assert result.output == 'Opt: None  Arg: An Arg\n'


if __name__ == '__main__':
    test_hello_world()

click_prog.py

import click
import sys


@click.command()
@click.option('--opt')
@click.argument('arg')
def hello(arg, opt):
    """A Simple program"""
    click.echo('Opt: {}  Arg: {}'.format(opt, arg))


if __name__ == '__main__':
    hello(sys.argv[1:])

pytest.ini

[pytest]

# -- recommended but optional:
# python_files = tests.py test_*.py *_tests.py

测试结果:

===================== test session starts =====================
platform darwin -- Python 3.6.5, pytest-3.7.1, py-1.5.4, pluggy-0.7.1
rootdir: /Users/strauch/dev/fix_windows, inifile: pytest.ini
plugins: xdist-1.22.5, forked-0.2, cov-2.6.0
collected 1 item

test_click.py .                                         [100%]

---------- coverage: platform darwin, python 3.6.5-final-0 -----------
Name             Stmts   Miss  Cover
------------------------------------
click_prog.py        8      1    88%
test_click.py       12      1    92%
------------------------------------
TOTAL               20      2    90%


================== 1 passed in 0.07 seconds ===================

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-09
    • 2018-10-29
    • 1970-01-01
    • 1970-01-01
    • 2013-11-07
    相关资源
    最近更新 更多