我不确定这是否最适合这里或 Programmers Stack Exchange,但我会先在这里尝试,如果不合适,我会在那里交叉发布。
我最近开发了一个 Web 服务,我正在尝试创建一个基于 Python 的命令行界面,以使其更易于交互。我一直在使用 Python 进行简单的脚本编写,但我在创建完整的包(包括 CLI 应用程序)方面缺乏经验。
我研究了不同的包来帮助创建 CLI 应用程序,我决定使用click。我关心的是如何构建我的应用程序以使其在我真正开始将它们组合在一起之前彻底可测试,以及如何使用 click 来帮助解决这个问题。
我已经阅读了关于测试的 click 文档并检查了API 的相关部分,虽然我已经设法使用它来测试简单的功能(在作为参数传递给我的 CLI 时验证--version
和--help
工作),但我不确定如何处理更高级的测试用例。
我将提供一个具体示例来说明我现在正在尝试测试的内容。我计划让我的应用程序具有以下类型的架构......
...其中CommunicationService
封装了通过 HTTP 连接和直接与 Web 服务通信所涉及的所有逻辑。我的 CLI 为 Web 服务主机名和端口提供默认值,但应允许用户通过显式命令行参数、编写配置文件或设置环境变量来覆盖这些:
@click.command(cls=TestCubeCLI, help=__doc__)
@click.option('--hostname', '-h',
type=click.STRING,
help='TestCube Web Service hostname (default: {})'.format(DEFAULT_SETTINGS['hostname']))
@click.option('--port', '-p',
type=click.IntRange(0, 65535),
help='TestCube Web Service port (default: {})'.format(DEFAULT_SETTINGS['port']))
@click.version_option(version=version.__version__)
def cli(hostname, port):
click.echo('Connecting to TestCube Web Service @ {}:{}'.format(hostname, port))
pass
def main():
cli(default_map=DEFAULT_SETTINGS)
我想测试如果用户指定不同的主机名和端口,那么Controller
将CommunicationService
使用这些设置而不是默认值来实例化 a。
我想做到这一点的最好方法是这样的:
def test_cli_uses_specified_hostname_and_port():
hostname = '0.0.0.0'
port = 12345
mock_comms = mock(CommunicationService)
# Somehow inject `mock_comms` into the application to make it use that instead of 'real' comms service.
result = runner.invoke(testcube.cli, ['--hostname', hostname, '--port', str(port)])
assert result.exit_code == 0
assert mock_comms.hostname == hostname
assert mock_comms.port == port
如果我能得到关于如何正确处理这种情况的建议,我应该能够接受它并使用相同的技术来使我的 CLI 的每个其他部分都可测试。
对于它的价值,我目前正在使用 pytest 进行测试,这是我到目前为止所进行的测试的范围:
import pytest
from click.testing import CliRunner
from testcube import testcube
# noinspection PyShadowingNames
class TestCLI(object):
@pytest.fixture()
def runner(self):
return CliRunner()
def test_print_version_succeeds(self, runner):
result = runner.invoke(testcube.cli, ['--version'])
from testcube import version
assert result.exit_code == 0
assert version.__version__ in result.output
def test_print_help_succeeds(self, runner):
result = runner.invoke(testcube.cli, ['--help'])
assert result.exit_code == 0