はじめに
実はいくおな、nikkieです。
PyCon mini 東海 2024をきっかけにRustでCLIを作ることに興味を持ちました。
これまでのアウトプットと絡めて小さく素振りしていきます。
目次
「pytestでRust製CLIをe2eテストしてみよう」
attakeiさんによるトーク。
https://tokai.pycon.jp/2024/#event-talk-2
自作されたageを題材に、pytestでe2eを書いているというお話でした1。
聞く中で私はageの実装にも興味を持ちます。
CLAPなるものを使っているようでした
Rustのclapってやつがけっこうよさそう #pycontokai https://t.co/upQ6lGlArz
— nikkie / にっきー (@ftnext) 2024年11月16日
argparseとかclickの練習プログラムをclapでやってみるか〜
また、過去に『Rustの練習帳』で、出力するだけと簡単なコマンドのe2eを書いたことも思い出しました。
assert_cmdというものを使います。
手前味噌ですが、Rustでコマンドラインツールのe2eやる例が書籍にありました。clapでも当てはまるかは未確認です #pycontokai
— nikkie / にっきー (@ftnext) 2024年11月16日
『Rustの練習帳』1章 assert_cmdを使ったテストの実装についてドキュメントで調べたことメモ - nikkie-ftnextの日記 https://t.co/bswbOOjbWU
CLAPの素振り:まずは位置引数だけを扱ってみよう!
RustでCLIとそのテストを実装してみたくなった私が思い付いたのは、過去に書いたプログラムの書き換えです。
私はCLIに思い入れがあり、Pythonのargparseで色々と書いてきました2。
ハードルを下げるためにとにかく小さくしたかったので(だってRustは難しいじゃないですか!)、最初は位置引数だけ扱ってみることにしました。
これだけでも私には冒険です!
argparseバージョンの記事はこちら
$ python repeat.py シオン 3 シオンシオンシオン
これをRustでCLAPを使って書いていきます。
できあがったのがこちら
$ cargo run --quiet シオン 3 シオンシオンシオン
CLAPで位置引数を扱う
Command Line Argument Parser
CLAPのドキュメントから、「Command Line Applications in Rust」を見つけます。
grepを再実装していく(その名もgrrs
)のですが、位置引数の扱いは1.2にありました。
https://rust-cli.github.io/book/tutorial/cli-args.html#parsing-cli-arguments-with-clap
CLAPのドキュメントのExampleも合わせて参考にします。
https://docs.rs/clap/latest/clap/#example
Cargo.toml(該当箇所のみ)
[dependencies] clap = { version = "4", features = ["derive"] }
src/main.rs3
use clap::Parser; #[derive(Parser)] struct Cli { string: String, number: usize, } fn main() { let args = Cli::parse(); println!("{}", args.string.repeat(args.number)); }
derive featureなるものがまるで魔法ですね。
これでargparseと同じ振る舞いのCLIが実装できました!
追加の学び
位置引数にデフォルト値を与える
これはめちゃめちゃ簡単でした(魔法だ...)
#[derive(Parser)] struct Cli { string: String, + #[clap(default_value = "3")] number: usize, }
デフォルト値を与えたことで、argparse版同様に省略できる位置引数となりました
$ cargo run --quiet シオン シオンシオンシオン
assert_cmdでコマンドのテスト
tests/cli.rs
use assert_cmd::Command; #[test] fn 文字列と回数を指定して繰り返す() { let mut cmd = Command::cargo_bin("repeat").unwrap(); let assert = cmd.arg("大好き").arg("5").assert(); assert.success().stdout("大好き大好き大好き大好き大好き\n"); }
今回のCLIは出力するだけなので、assert_cmd
を使ってテストも書けています。
正常終了して、かつ、標準出力がこれこれと検証していますね。
$ cargo test running 2 tests test 文字列だけ指定した場合は3回繰り返す ... ok test 文字列と回数を指定して繰り返す ... ok
終わりに
PyCon mini 東海でぐぐっと上がったRust製CLIへのモチベーションから、CLAPを素振りしました。
- 位置引数だけのCLIが作れました!
- derive feature、すっげ〜(どういう仕組みなの!?)
- assert_cmdも使ってテストも合わせて書けています
argparseの経験や『Rustの練習帳』の素振りの経験が重なって、今回のトピックは小さな背伸びという感じでした。
引き続きCLAPを素振りしていくぞ!(Next...)
今回のソースコードはこちらです
-
pytestについても多くを学べる発表でした
↩聞きに来たぞ〜。このスライドで、私がワークショップで伝えたいpytestのことほぼカバーしてる! #pycontokai https://t.co/7BXIwPIR6t
— nikkie / にっきー (@ftnext) 2024年11月16日 - argparseの技術同人誌も書きました ↩
-
main.rsの
main
関数に書くことを思い出しました ↩