Claude Codeでclap製CLIをnextestで回す — 長い出力の絞り方
clapのderiveでRustのCLIを作り、cargo nextestで回す設定をまとめます。テスト出力を失敗分だけに絞るprofileとCLAUDE.mdの書き方を扱います。
RustのCLIをclapで作り、テストをcargo nextestで回すなら、Claude Codeに渡す出力を失敗分だけに絞るのが肝です。cargo testの全件ログをそのまま読ませるとコンテキストを食いますが、nextestにはその絞り込みが設定として用意されています。
この記事では、clapのderiveで骨格を作る手順、nextestの導入、出力を絞るprofile、CLAUDE.mdとpermissionsの書き方を順に扱います。
clapのderiveでCLIの骨格を作る
clapはRustのコマンドライン引数パーサーです。構造体に#[derive(Parser)]を付けると、フィールドがそのまま引数になります。deriveを使うには、derive機能を有効にして追加します。
cargo add clap --features deriveclapのチュートリアルにあるクイックスタートは、サブコマンドと省略形つきのオプションを持つ形です。次のコードは、その構成に沿って縮めた例です。
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
/// Turn debugging information on
#[arg(short, long, action = clap::ArgAction::Count)]
debug: u8,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// does testing things
Test {
/// lists test values
#[arg(short, long)]
list: bool,
},
}
fn main() {
let cli = Cli::parse();
// 以降にプログラム本体を書く
}押さえておくのは3点です。
#[command(version, about)]を付けると、--versionと--helpの文言がCargo.tomlから埋まります- サブコマンドは
#[derive(Subcommand)]のenumで表します。各サブコマンドが固有の引数を持てます - 取りうる値が決まっている引数は
ValueEnumのenumにします。想定外の値は、clapが可能な値を示すエラーで終了させます
取りうる値が決まっている引数は、ValueEnumを派生させたenumにします。
use clap::{Parser, ValueEnum};
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
/// What mode to run the program in
#[arg(value_enum)]
mode: Mode,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Mode {
/// Run swiftly
Fast,
/// Crawl slowly but steadily
Slow,
}--helpには[possible values: fast, slow]のように選択肢が並び、範囲外の値を渡すと、clapが可能な値を示して終了します。バリアントのdocコメントが、そのままヘルプ文になる点も覚えておくと、Claudeにコメントを書かせる意味が変わります。
引数の定義は型で決まるため、Claudeに任せやすい領域です。ただし何を作るかの指示は、後述のCLAUDE.mdに規約として置いておくと手戻りが減ります。
cargo nextestで回す準備
nextestはRust向けのテストランナーで、cargo testの代わりにcargo nextest runで使います。公式サイトは「cargo testの最大3倍速」をうたい、失敗の見やすさや、テスト単位の設定を特長に挙げています。
導入は、ビルド済みバイナリを入れるのが近道です。cargo-binstallがあれば次の1行で済みます。
cargo binstall cargo-nextest --secure
cargo nextest run動きについて、Claudeに伝えておきたい性質が3つあります。
- 既定では最初の失敗で新規テストの実行を止めます。実行中のテストは完走させます。反復の途中なら、この挙動がそのまま出力の短縮になります
- doctestは対象外です。公式は、stable Rustの制約でdoctestは未対応としており、別工程で
cargo test --docを実行するよう案内しています - 実行できるテストが0件のとき、既定の挙動は失敗です。
--no-testsでwarnやpassに変えられます
CLIがsrc/main.rsだけのバイナリクレートなら、doctestを書く場面はほとんどありません。ライブラリ部分を切り出したときに、cargo test --docが必要になります。
テスト出力が長いときの絞り方
nextestの出力は、4つの軸で調整できます。コマンドラインでも、.config/nextest.tomlのprofileでも指定できます。
| 軸 | フラグ | 既定 | 役割 |
|---|---|---|---|
| 実行中に出す状態 | フラグ--status-level | 既定pass | 役割PASS行まで出すか |
| 終了時に出す状態 | フラグ--final-status-level | 既定設定リファレンスではflaky | 役割最後の一覧に何を残すか |
| 失敗テストの出力 | フラグ--failure-output | 既定immediate | 役割stdout・stderrをいつ出すか |
| 成功テストの出力 | フラグ--success-output | 既定never | 役割通常は触らない |
--status-levelはnone、fail、retry、slow、leak、pass、skip、allの順で、右ほど多くを出します。failにすれば、PASS行が消えて失敗だけが残ります。
--failure-outputはimmediate、immediate-final、final、neverの4値です。finalなら失敗テストの出力が実行の最後にまとまって出ます。途中のログが流れないぶん、読ませる側は最後の塊だけを見れば済みます。
これらをまとめた手元用の呼び出しは、次の形です。
cargo nextest run --status-level fail --failure-output final進捗の表示を抑えたいだけなら、--show-progress=onlyも選べます。これは--show-progress=bar --status-level=slow --final-status-level=noneと同じ意味で、成功したテストの出力を隠します。
全件を走らせず絞り込む
ログを短くする最も直接的な方法は、実行するテストを減らすことです。テスト名を引数に渡せば、一致するものだけが動きます。
cargo nextest run parse
cargo nextest run -p my-cli
cargo nextest run -E 'test(parse) - test(/slow/)'3行目の-Eは、nextestのfiltersetという式言語です。test()、package()、binary()などの述語を、+(和)、&(積)、-(差)、notで組み合わせます。cargo test -- --skipで書いていた除外が、- や not で表せます。
テスト名の一覧を先に見たいときは、cargo nextest listが使えます。Claudeに「どのテストが存在するか」を確かめさせてから、狙い撃ちさせる流れが組めます。
失敗したテストだけを再実行する
--rerunで、前回の記録された実行のうち失敗した、または完了しなかったテストだけを再実行できます。ただしこの記録機能は実験的で、ユーザー設定の~/.config/nextest/config.tomlで有効にする必要があります。
[experimental]
record = true
[record]
enabled = true有効にすると、以降のcargo nextest runが自動で記録されます。保持の既定は、1ワークスペースあたり30日、100実行、圧縮後1GBのいずれかまでです。実験段階の機能なので、プロジェクトの標準手順にはせず、手元の高速化として使う位置づけが無難です。
ログを見たいときと、不安定なテスト
絞った出力で原因が分からないときは、--no-captureが使えます。テストの標準出力と標準エラーを、そのままターミナルに流します。このモードではテストが直列で動くため、複数のテストのログが混ざりません。cargo test -- --nocaptureは並列のままなので、ここが違います。
実行中のテストが止まって見えるときは、対話端末でtキーを押すと、実行中の各テストの状態と、それまでに集まった出力が表示されます。パイプ越しの実行では使えません。
間欠的に落ちるテストには、--retriesがあります。--retries 2は最大3回の試行を意味し、途中で成功すればそのテストはFLAKYとして扱われ、ほかに失敗がなければ終了コードは0です。Claudeに「通るまで回す」判断を任せる前に、この既定を知っておくと、flakyな成功を見落とさずに済みます。再試行の待ち時間は、設定ファイル側でだけ指定できます。
profileでClaude用とCI用を分ける
nextestは、設定をprofileで切り替えます。リポジトリ共通の設定は、ワークスペースルートの.config/nextest.tomlに置きます。profileは-P(--profile)で選びます。
Claudeに読ませる用のprofileを作る例です。
# .config/nextest.toml
[profile.agent]
status-level = "fail"
final-status-level = "fail"
failure-output = "final"
[profile.ci]
fail-fast = falseagentは出力を失敗分に絞り、ciは失敗が出ても全件を走らせます。公式の設定ページも、CIでは最初の失敗で中断しないprofileを推奨しています。カスタムprofileは、既定ではprofile defaultの設定を継承します。名前をdefault-で始めるのは避けます。将来、nextest側の組み込みprofileと衝突するためです。
実行はcargo nextest run -P agentです。設定の優先順位は、コマンドライン引数、環境変数、profileの順です。agentを使っていても、--status-level passを付ければPASS行が出ます。
CLAUDE.mdに書く規約
CLAUDE.mdは強制ではなく、Claudeが読む文脈です。Claude Codeの公式ドキュメントは、検証できる具体さで書くことと、1ファイル200行未満を目安にすることを勧めています。
Rust CLIの開発なら、次のような断片が出発点になります。
## Rust CLI 規約
- CLI引数は clap の derive で定義する(builder API は使わない)
- テストは `cargo nextest run -P agent` で実行する。`cargo test` は doctest 用の `cargo test --doc` にだけ使う
- 失敗したテストの出力は最後にまとまって出る。全文を貼り直さず、FAIL の行とその直下だけを読む
- 全件実行の前に、`cargo nextest run <テスト名>` で対象を絞る
- 引数の定義を変えたら `verify_cli` テストを必ず通す3行目と4行目が、出力を絞る規約の中心です。-P agentだけを書いておけば、profileの中身を変えても指示を直さずに済みます。5行目のverify_cliは、次の節で扱うclapのテストです。
CLAUDE.mdがさらに長くなるときは、記事ごとに切り出す手もあります。CLAUDE.mdをSkillsに移行してコストを削減する方法が、その判断基準を扱っています。Pythonでの同じ作りはpytestの規約をCLAUDE.mdに書く方法にあります。
clapの定義を検証するテスト
clapのチュートリアルは、定義の誤りの多くがdebug_assert!として報告される、と説明しています。サブコマンドを1つずつ確かめる代わりに、Command::debug_assertを呼ぶテストを1本置くのが推奨の形です。
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert();
}これをcargo nextest run verify_cliで回すと、引数の定義ミスをビルド後すぐに拾えます。Claudeに引数を足させる反復では、この1本が最小の検証になります。
permissionsでcargoコマンドを許可する
反復の間、Claudeは同じコマンドを何度も実行します。確認の手間を省くため、.claude/settings.jsonにルールを書きます。
{
"permissions": {
"allow": [
"Bash(cargo check *)",
"Bash(cargo nextest run *)",
"Bash(cargo nextest list *)",
"Bash(cargo clippy *)"
],
"ask": [
"Bash(cargo add *)"
],
"deny": [
"Bash(cargo publish *)"
]
}
}ルール記法の要点は2つです。末尾に空白を挟んだ*は、引数なしの素のコマンドにも一致します。*はサブコマンドの後ろに置きます。Bash(cargo *)と書くとcargo publishまで通るためです。cargo fmt && cargo checkのような連結コマンドは、サブコマンドごとに独立して照合されます。
cargo nextest runは、cargoの後ろにサブコマンドが2語続く形です。Bash(cargo nextest run *)のように、実行する動詞まで書いて許可します。同様の許可設計は、Axumの開発でcargo check・clippy・testに対して扱っています。
つまずきやすい点
テストが0件でエラーになる。バイナリクレートにテストが1本もないと、既定の--no-testsが失敗として扱われます。最初のテストを書くまでは--no-tests=warnを付けます。
doctestが走らない。nextestはdoctestを実行しません。ライブラリを持つ構成では、cargo test --docを別の手順としてCLAUDE.mdに書いておきます。
終了時の一覧が想定と違う。 --final-status-levelの既定は、ページによって記述が食い違っています。リファレンスはflaky、レポートの解説ページはfailとしています。揃えて使いたいときは、profileで値を明示します。
遅いテストにSLOWが付く。60秒を超えたテストには、既定でSLOWの印が付きます。CLIが外部コマンドを呼ぶテストでは、時間の見込みを確かめておくと、誤検知と見分けられます。
絞った出力が反復を安定させる
clapで定義を型に寄せ、nextestで出力を失敗分に絞ると、Claudeに渡す情報量が小さくなります。規約として残すのは、-P agentの1行とverify_cliの1本、それにdoctestだけ別にする一文です。
同じ考え方は、ほかの言語でも使えます。たとえばSpring BootのREST API開発では、ビルドツールのコマンドを許可する設定を扱っています。