2019」20日目の記事です。2019年9月からJX通信社のエンジニアとなった鈴木(泰)です。好きな食べ物はオムライスです。"その1"とした理由は、アドカレに間に合わな(小声)・・・じゃなかった・・・この記事だけで全ての備忘録を列挙すると長くなりすぎてしまい、記事が読み難くなると判断したからです。今後も引き続き少しづつ備忘録を紹介していければと思います。はじめに私はCLIをよく書きます。その理由は、バックエンドシステムの運用業務に携わっていることにあります。運用業務では様々な場面でCLIを作成します。私の場合、運用業務における手作業を自動化するため、バッチ処理を書くため、というのが主です。関わらず、私はCLIのユニットテストを、これまであまり書いてきませんでした。これについてはとても反省しています。言い訳)は多々あります。書き捨てスクリプトだから。外部コンポネントと密結合しているので、ユニットテストが書き難い。テストを書く時間がない。テストを書くことは、オーバーワーク。...ざっくりとひとまとめにすると、要は「書いている時間がない」のです。書いている時間がない場合どうするか?私がよく使う手はコピー・ペーストです。CLIを書くときに頻出するパターンを想定した、コピーペースト用のスニペットを用意しておけば良いのです。今回は、CLIを書くときに頻出するパターン毎に、コピーペースト用のスニペットを書いてみることにしました。対象とするCLI対象とするのは、外部コンポネントとの結合がないCLIのユニットテストです。外部コンポネントとの結合とは、ファイルシステムやネットワーク通信を介してシステム繋がりのことです。WebAPIから何らかのデータを取得し、結果をローカルファイルに保存するCLIは、外部コンポネントとの結合があるCLIです。WebAPIとローカルファイルシステムという、2つのコンポネントとつながっているからです。clickclickというライブラリを用いてCLIを書きます。clickを用いる理由は標準ライブラリ(argparse,getopt)を用いてCLIを作るよりも簡潔に書くことができるclickの開発者がユニットテスト用のモジュールを提供してくれているので使ってみたい私のお気に入りだからといったことがあります。10.15.2、Python3.7、こちらのclickおよびその依存パッケージのバージョンで動作確認をしております。私がよく使うスニペットを前提に書いています。私があまり使わないなぁ・・・と思ったら、そのスニペットは書いていません。(・・・が、こんなスニペットも使える!とか、これは必要だろ!とかあればコメントお願いします!)本記事で使ったソースコードはこちらにあります。エラー出力、終了ステータスコードの検証まずは、必ず検証すべき3つの値(標準出力、標準エラー出力、終了ステータスコード)からです。`CliRunner(mix_stderr=False)`とする。100clickを使用する場合click.echo関数を用いて出力することが慣習ですが、print関数で出力した場合でも、上のテストは動きます。codeを用いて、出力される文字列に色を付与できます。codeを無視できます。codeを含めた出力の検証をしたい場合、CliRunner(color=True)とすればできます。例外を投げるCLIの検証です。type(result.exception)例外がない場合、result.exceptionはNoneです。コマンドライン引数やオプションの検証clickでは、コマンドライン引数やオプション指定を間違えていた場合、デフォルトでは終了ステータス2(より厳密には、staticな変数click.exceptions.UsageError.exit_codeの値)で終了します。私としては、CLIの引数の検証(例えば、このオプションが必須で・・・この引数は文字列で・・・といったような仕様の検証)は不要であり、終了ステータスだけ検証すれば良いと思います。CLIの引数周りの処理はclickライブラリが担う責務であり、利用者である私たちが検証すべきことではないからです。result.exit_codeサブコマンドのテストの書き方サブコマンドのテストは次のように書きます。result.output環境変数の検証clickでは、CliRunnerのenvという引数に、テストで使用する環境変数を指定できます。result.output標準入力clickには、標準入力からデータを読むためのget_text_stream関数があるのですが、この関数を使った場合のユニットテストの書き方はわかりませんでした。この記事に掲載するなよ!と思われるかもしれませんが、もしかしたら誰か良い方法を知っているかもしれないということで・・・記事に掲載することとしました。書いたら良いかわかりませんでした・・・。標準入力2標準入力を読み込むために、clickのget_text_stream関数ではなくinput関数を使用した場合であれば、次のようにしてユニットテストを書くことができます。今回は、外部コンポネントとの結合がない場合のみなので、あまり迷うことはありませんでした。その理由はclick.testing.CliRunnerが用意されていることにあると思います。これのおかげで「こういう場合はこう書けば良い」という方針が明確になっています。次回は、外部コンポネントとの結合がある場合のスニペットについて書く予定です。ファイルシステムに読み書きする、HTTP通信する、MySQLに接続する、ロガーを通してアプリケーションログを残す、については頻出パターンなので、スニペットを用意したいと考えています。外部コンポネントとの結合部をどうモックするか?スニペットし易い簡潔なモックをどう書くか?というところが鍵となりそうだと考えています。ありがとうございました。asyncioエンジニアの鈴木(泰)です。Corp.引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。