Claude Media
Claude Codeでclap製CLIをnextestで回す — 長い出力の絞り方

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 derive

clapのチュートリアルにあるクイックスタートは、サブコマンドと省略形つきのオプションを持つ形です。次のコードは、その構成に沿って縮めた例です。

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つあります。

  1. 既定では最初の失敗で新規テストの実行を止めます。実行中のテストは完走させます。反復の途中なら、この挙動がそのまま出力の短縮になります
  2. doctestは対象外です。公式は、stable Rustの制約でdoctestは未対応としており、別工程でcargo test --docを実行するよう案内しています
  3. 実行できるテストが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 = false

agentは出力を失敗分に絞り、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開発では、ビルドツールのコマンドを許可する設定を扱っています。

この記事を共有:XはてブLinkedIn