Tutorial¶
Let’s assume you use pytest for running your tests, which is certainly a
good idea. Your CLI program is called foobar
.
You have prepared a setup.py
with a CLI entrypoint. For the tests you have
prepared a tests/
folder (outside of foobar/
, because you don’t want
your tests to be packaged up with your application code).
Then your directory layout looks somewhat like one of our examples.
Note
You can easily generate a CLI project of your own from one of the examples of Python CLI test helpers using Copier, e.g.
$ copier copy gh:painless-software/python-cli-test-helpers my-cli
Functional tests¶
Start with a simple set of functional tests:
Is the entrypoint script installed? (tests the configuration in your setup.py)
Can this package be run as a Python module? (i.e. without having to be installed)
Is command XYZ available? etc. Cover your entire CLI usage here!
This is almost a stupid exercise: Run the command as a shell command and inspect the exit code of the exiting process, e.g.
def test_runas_module():
"""Can this package be run as a Python module?"""
result = shell('python -m foobar --help')
assert result.exit_code == 0
def test_entrypoint():
"""Is entrypoint script installed? (setup.py)"""
result = shell('foobar --help')
assert result.exit_code == 0
The trick is that you run a non-destructive command, e.g. by using the usual
--help
option of every command. This should cover your entire CLI user
interface definition.
See more example code.
Unit tests¶
Then you’re ready to take advantage of our helpers.
ArgvContext
¶
ArgvContext
allows you to mimic the use of specific CLI arguments:
def test_get_action():
"""Is action argument (get/set) available?"""
with ArgvContext('foobar', 'get'):
args = foobar.cli.parse_arguments()
assert args.action == 'get'
If you don’t have argument parsing in a dedicated function you can combine this approach with mocking a target function, e.g.
@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
See more example code.
EnvironContext
¶
EnvironContext
allows you to mimic the presence (or absence) of
environment variables:
def test_fail_without_secret():
"""Must fail without a ``SECRET`` env variable specified"""
message_regex = "Environment value SECRET not set."
with EnvironContext(SECRET=None):
with pytest.raises(SystemExit, match=message_regex):
foobar.command.baz()
pytest.fail("CLI doesn't abort with missing SECRET")
See more example code.