I'm not sure if this is the best fit for here or the Programmers Stack Exchange, but I'll try here first and cross-post this over there if it's not appropriate.
I've recently developed a web service and I'm trying to create a Python-based command-line interface to make it easier to interact with. I've been using Python for a while for simple scripting purposes but I'm inexperienced at creating full-blown packages, including CLI applications.
I've researched different packages to help with creating CLI apps and I've settled on using click. What I'm concerned about is how to structure my application to make it thoroughly testable before I actually go about putting it all together, and how I can use click to help with that.
I have read click's documentation on testing as well as examined the relevant part of the API and while I've managed to use this for testing simple functionality (verifying --version
and --help
work when passed as arguments to my CLI), I'm not sure how to handle more advanced test cases.
I'll provide a specific example of what I'm trying to test right now. I'm planning for my application to have the following sort of architecture...
...where the CommunicationService
encapsulates all logic involved in connecting and directly communicating with the web service over HTTP. My CLI provides defaults for the web service hostname and port but should allow users to override these either through explicit command-line arguments, writing config files or setting environment variables:
@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)
I want to test that if the user specifies different hostnames and ports, then Controller
will instantiate a CommunicationService
using these settings and not the defaults.
I imagine that the best way to do this would be something along these lines:
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, ['--host', hostname, '--port', str(port)])
assert result.exit_code == 0
assert mock_comms.hostname == hostname
assert mock_comms.port == port
If I can get advice on how to properly handle this case, I should hopefully be able to pick it up and use the same technique for making every other part of my CLI testable.
For what it's worth, I'm currently writing tests in pytest and this is the extent of the tests I've got so far:
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'], foo='bar')
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
Aucun commentaire:
Enregistrer un commentaire