Python CLI程序单元测试

Reo*_*orx 20 python testing unit-testing command-line-interface

我正在开发一个python命令行界面程序,我在测试时发现它很无聊,例如,这里是程序的帮助信息:

usage: pyconv [-h] [-f ENCODING] [-t ENCODING] [-o file_path] file_path

Convert text file from one encoding to another.

positional arguments:
  file_path

optional arguments:
  -h, --help            show this help message and exit
  -f ENCODING, --from ENCODING
                        Encoding of source file
  -t ENCODING, --to ENCODING
                        Encoding you want
  -o file_path, --output file_path
                        Output file path
Run Code Online (Sandbox Code Playgroud)

当我对程序进行更改并想要测试某些内容时,我必须打开一个终端,输入命令(带有选项和参数),输入enter,然后查看运行时是否发生任何错误.如果确实发生了错误,我必须回到编辑器并从头到尾检查代码,猜测bug的位置,进行小的更改,写print行,返回终端,再次运行命令......

递归.

所以我的问题是,使用CLI程序进行测试的最佳方法是什么,它可以像使用普通python脚本进行单元测试一样简单吗?

hpk*_*k42 13

我认为在整个程序级别上进行功能测试是完全可以的.每次测试仍然可以测试一个方面/选项.通过这种方式,您可以确保程序真正起作用.编写单元测试通常意味着您可以更快地执行测试,并且通常更容易理解或理解故障.但是单元测试通常更多地与程序结构相关联,当您在内部更改内容时需要更多的重构工作.

无论如何,使用py.test,这是一个测试pyconv ::的latin1到utf8转换的小例子::

# content of test_pyconv.py

import pytest

# we reuse a bit of pytest's own testing machinery, this should eventually come
# from a separatedly installable pytest-cli plugin. 
pytest_plugins = ["pytester"]

@pytest.fixture
def run(testdir):
    def do_run(*args):
        args = ["pyconv"] + list(args)
        return testdir._run(*args)
    return do_run

def test_pyconv_latin1_to_utf8(tmpdir, run):
    input = tmpdir.join("example.txt")
    content = unicode("\xc3\xa4\xc3\xb6", "latin1")
    with input.open("wb") as f:
        f.write(content.encode("latin1"))
    output = tmpdir.join("example.txt.utf8")
    result = run("-flatin1", "-tutf8", input, "-o", output)
    assert result.ret == 0
    with output.open("rb") as f:
        newcontent = f.read()
    assert content.encode("utf8") == newcontent
Run Code Online (Sandbox Code Playgroud)

安装pytest("pip install pytest")之后你可以像这样运行::

$ py.test test_pyconv.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.4.5dev1
collected 1 items

test_pyconv.py .

========================= 1 passed in 0.40 seconds =========================
Run Code Online (Sandbox Code Playgroud)

该示例通过利用pytest的fixture机制重用了pytest自己测试的一些内部机制,请参阅http://pytest.org/latest/fixture.html.如果您暂时忘记了详细信息,可以通过"run"和"tmpdir"来帮助您准备和运行测试.如果你想玩,你可以尝试插入一个失败的assert语句或简单地"断言0",然后查看回溯或发出"py.test --pdb"进入python提示符.


Max*_*xim 6

所以我的问题是,使用 CLI 程序进行测试的最佳方法是什么,它可以像使用普通 python 脚本进行单元测试一样简单吗?

唯一的区别是,当您将 Python 模块作为脚本运行时,其__name__属性设置为'__main__'. 所以一般来说,如果您打算从命令行运行脚本,它应该具有以下形式:

import sys

# function and class definitions, etc.
# ...
def foo(arg):
    pass

def main():
    """Entry point to the script"""

    # Do parsing of command line arguments and other stuff here. And then
    # make calls to whatever functions and classes that are defined in your
    # module. For example:
    foo(sys.argv[1])


if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

现在,如何使用它没有区别:作为脚本或作为模块。因此,在单元测试代码中,您只需导入foo函数、调用它并做出您想要的任何断言。


Jua*_*rez 6

也许太少太晚了,但你总是可以使用

import os.system
result =  os.system(<'Insert your command with options here'>
assert(0 == result)
Run Code Online (Sandbox Code Playgroud)

这样,您可以像从命令行一样运行程序,并评估退出代码。

(学习pytest后更新)你也可以使用capsys。(来自运行 pytest --fixtures)

sys.stdoutcapsys 启用对和的写入的文本捕获sys.stderr

The captured output is made available via ``capsys.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
Run Code Online (Sandbox Code Playgroud)


Pet*_*ino 6

从用户界面开始进行功能测试,然后逐步进行单元测试。感觉很困难,尤其是当您使用argparse模块或click包时,它们控制了应用程序入口点。

CLI-测试助手Python包有对您的CLI编写测试一种全面的方法的例子和辅助功能(上下文管理器)。这是一个简单的想法,并且与 TDD 完美配合:

  1. 从功能测试开始(以确保您的用户界面定义)和
  2. 致力于单元测试(以确保您的实施合同)

功能测试

注意:我假设您开发的代码与setup.py文件一起部署或作为模块运行 ( -m)。

  • 是否安装了入口点脚本?(测试 setup.py 中的配置)
  • 这个包可以作为 Python 模块运行吗?(即无需安装)
  • 命令 XYZ 是否可用?等等。在这里涵盖您的整个 CLI 用法!

这些测试很简单:它们运行您将在终端中输入的 shell 命令,例如

def test_entrypoint():
    exit_status = os.system('foobar --help')
    assert exit_status == 0
Run Code Online (Sandbox Code Playgroud)

请注意使用非破坏性操作(例如--help--version)的技巧,因为我们无法使用此方法模拟任何内容。

走向单元测试

为了测试单一方面的内部应用程序,你需要像命令行参数,也许环境变量模仿的东西。您还需要捕获脚本的退出,以避免测试因SystemExit异常而失败。

ArgvContext模拟命令行参数的示例:

@patch('foobar.command.baz')
def test_cli_command(mock_command):
    """Is the correct code called when invoked via the CLI?"""
    with ArgvContext('foobar', 'baz'), pytest.raises(SystemExit):
        foobar.cli.main()

    assert mock_command.called
Run Code Online (Sandbox Code Playgroud)

请注意,我们模拟了我们希望 CLI 框架(click在此示例中)调用的函数,并且我们捕获SystemExit了框架自然引发的函数。上下文管理器由cli-test-helperspytest 提供

单元测试

剩下的一切照常营业。通过上述两种策略,我们克服了 CLI 框架可能剥夺了我们的控制权。剩下的就是通常的单元测试。希望采用 TDD 风格。

披露:我是cli-test-helpersPython 包的作者。