これがここに最適なのか、Programmers Stack Exchange に最適なのかはわかりませんが、最初にここで試して、適切でない場合は別の場所にクロスポストします。
私は最近、Web サービスを開発しました。Python ベースのコマンドライン インターフェイスを作成して、より簡単に操作できるようにしようとしています。簡単なスクリプト作成の目的でしばらくの間 Python を使用してきましたが、CLI アプリケーションを含む本格的なパッケージの作成には経験がありません。
CLI アプリの作成に役立つさまざまなパッケージを調査し、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
これを行う最善の方法は、次のようなものになると思います。
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