MCP HubMCP Hub
스킬 목록으로 돌아가기

test-cli-application

pjt222
업데이트됨 Yesterday
1 조회
17
2
17
GitHub에서 보기
테스팅testingapidesign

정보

이 스킬은 Node.js CLI 애플리케이션의 통합 테스트를 내장된 node:test 모듈을 사용해 작성하는 데 도움을 줍니다. 명령어 실행, 출력 검증, 파일 시스템 상태 확인, 오류 케이스 테스트와 같은 주요 패턴을 다룹니다. 기존 CLI에 테스트를 추가하거나, 새로운 명령어를 테스트하거나, CLI 도구의 CI를 설정할 때 활용하세요.

빠른 설치

Claude Code

추천
기본
npx skills add pjt222/agent-almanac -a claude-code
플러그인 명령대체
/plugin add https://github.com/pjt222/agent-almanac
Git 클론대체
git clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/test-cli-application

Claude Code에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요

문서

Test a CLI Application

組込 node:test モジュールと execSync を使って Node.js CLI 用の統合テストを書く。

使用タイミング

  • 既存 CLI アプリケーションへのテスト追加
  • 新しく作成されたコマンドのテスト
  • ターゲットフレームワーク間のアダプタ/プラグイン挙動の検証
  • CLI 正確性を検証する CI のセットアップ
  • CLI 内部リファクタリング後のリグレッション捕捉

入力

  • 必須: CLI エントリポイントへのパス(例: cli/index.js
  • 必須: テストするコマンド
  • 任意: テストするフレームワークアダプタ(dry-run モード)
  • 任意: クリーンアップ要件(テストで作成されるファイル/symlink)

手順

ステップ1: テストインフラをセットアップする

import { describe, it, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { execSync } from 'child_process';
import { existsSync, rmSync } from 'fs';
import { resolve } from 'path';

const CLI = 'node cli/index.js';
const ROOT = process.cwd();

function run(args) {
  return execSync(`${CLI} ${args}`, {
    cwd: ROOT,
    encoding: 'utf8',
    timeout: 10000,
  });
}

主要設計判断:

  • node:test は組込 — テストランナー依存不要
  • execSync は CLI をサブプロセスとして実行 — 内部関数ではなく実バイナリをテスト
  • 10 秒タイムアウトが対話プロンプトでのハングを防ぐ
  • encoding: 'utf8' が regex マッチング用に文字列出力を与える
  • 再現性のためすべてのパスは ROOT 相対

期待結果: node:test からインポートし動く run() ヘルパーを持つテストファイル。

失敗時: node:test が利用不能なら、Node.js バージョンが 18 未満。アップグレードまたはポリフィルを使う。

ステップ2: スモークテストを書く

スモークテストは CLI が起動し、引数を解析し、期待される出力形を生むことを検証する:

describe('meta', () => {
  it('shows version', () => {
    const out = run('--version');
    assert.match(out, /\d+\.\d+\.\d+/);
  });

  it('shows help with all commands', () => {
    const out = run('--help');
    assert.match(out, /install/);
    assert.match(out, /list/);
    assert.match(out, /detect/);
  });
});

describe('registry', () => {
  it('list shows expected counts', () => {
    const out = run('list --domains');
    assert.match(out, /\d+ domains/);
  });

  it('search finds known items', () => {
    const out = run('search "docker"');
    assert.match(out, /result\(s\) for "docker"/);
  });

  it('search returns 0 for nonsense', () => {
    const out = run('search "xyzzy-nonexistent"');
    assert.match(out, /0 result/);
  });
});

スモークテストパターン:

  • --version--help は常に動く
  • レジストリロードがデータ整合性を検証
  • 既知と未知の用語での検索

期待結果: スモークテストが CLI が機能しデータがロードされていることを確認。

失敗時: レジストリカウントが頻繁に変わるなら、ハードコードされた数の代わりに \d+ を使う。

ステップ3: ライフサイクルテストを書く

ライフサイクルテストは create → verify → delete シーケンスをクリーンアップと共に検証する:

describe('install', () => {
  const testPath = resolve(ROOT, '.agents/skills/commit-changes');

  after(() => {
    // Always clean up, even if tests fail
    try { rmSync(testPath); } catch {}
    try { rmSync(resolve(ROOT, '.agents/skills'), { recursive: true }); } catch {}
    try { rmSync(resolve(ROOT, '.agents'), { recursive: true }); } catch {}
  });

  it('dry-run does not create files', () => {
    const out = run('install commit-changes --dry-run');
    assert.match(out, /DRY RUN/);
    assert.ok(!existsSync(testPath));
  });

  it('installs creates the target', () => {
    run('install commit-changes');
    assert.ok(existsSync(testPath));
  });

  it('skips already installed', () => {
    const out = run('install commit-changes');
    assert.match(out, /skipped/);
  });

  it('uninstall removes the target', () => {
    run('uninstall commit-changes');
    assert.ok(!existsSync(testPath));
  });
});

クリーンアップルール:

  • afterEach() ではなく after() フックを使う — ライフサイクルテストは互いに構築する
  • クリーンアップを try/catch で囲む — クリーンアップはテストスイートを失敗させてはならない
  • 葉から根へクリーン(ファイル → 親ディレクトリ → 祖父ディレクトリ)
  • テストが共有状態(symlink、設定ファイル)を変更したら、復元する

期待結果: テストが describe ブロック内で順次実行、失敗時もクリーンアップが実行される。

失敗時: テストが並列実行(node:test では非既定)したら、{ concurrency: 1 } で順次を強制する。

ステップ4: 各アダプタの dry-run テストを書く

変更を加えずに各アダプタのターゲットパスをテストする:

describe('adapter: cursor (dry-run)', () => {
  it('targets .cursor/skills/ path', () => {
    const out = run('install commit-changes --framework cursor --dry-run');
    assert.match(out, /\.cursor\/skills/i);
  });
});

describe('adapter: copilot (dry-run)', () => {
  it('targets .github/ path', () => {
    const out = run('install commit-changes --framework copilot --dry-run');
    assert.match(out, /\.github/i);
  });
});

このパターンは任意の数のアダプタにスケールする。各テスト:

  • 自動検出を迂回するため --framework を使う
  • ファイルが作成されないよう --dry-run を使う
  • ターゲットパスが出力に現れることをアサート

期待結果: アダプタ毎に 1 つの describe ブロック、各々が少なくともパスアサーションを持つ。

失敗時: プロジェクトにアダプタが存在しなければ、テストは "Unknown framework" で失敗する。これは正しい — アダプタテストは実装されたアダプタに対してのみ存在すべき。

ステップ5: エラーケーステストを書く

describe('errors', () => {
  it('rejects unknown items', () => {
    assert.throws(
      () => run('install nonexistent-skill-xyz'),
      /No matching items|Unknown/,
    );
  });

  it('rejects unknown framework', () => {
    assert.throws(
      () => run('install commit-changes --framework nonexistent'),
      /Unknown framework/,
    );
  });

  it('handles missing state gracefully', () => {
    assert.throws(
      () => run('scatter nonexistent-team'),
      /not burning|Unknown/,
    );
  });
});

エラーテストパターン:

  • assert.throwsexecSync からの非ゼロ終了コードを捕える
  • エラーメッセージ(stderr から取得)に regex マッチ
  • 「アイテムが見つからない」と「無効オプション」エラーの両方をテスト
  • エラーメッセージが是正措置を提案することを検証

期待結果: すべてのエラーパスが非ゼロ終了コードと助けになるメッセージを生む。

失敗時: execSync は非ゼロ終了で投げる。エラーの stderr または stdout がメッセージを含む。assert.throws regex がマッチしなければ error.stdout を確認する。

ステップ6: JSON 出力テストを書く

describe('json output', () => {
  it('campfire --json outputs valid JSON', () => {
    const out = run('campfire --json');
    const data = JSON.parse(out);
    assert.ok(typeof data.totalTeams === 'number');
    assert.ok(Array.isArray(data.fires));
  });

  it('gather --dry-run --json outputs structured data', () => {
    const out = run('gather tending --dry-run --json');
    // JSON may follow a DRY RUN header — extract from first '{'
    const jsonStart = out.indexOf('{');
    assert.ok(jsonStart >= 0, 'Should contain JSON');
    const data = JSON.parse(out.slice(jsonStart));
    assert.equal(data.team, 'tending');
  });
});

JSON テスト落とし穴:

  • 一部のコマンドは JSON を人間可読テキスト(例: DRY RUN ヘッダ)でプレフィックスする
  • 最初の { 文字を見つけて JSON を抽出
  • 構造(キーの存在、型)を検証、正確な値ではなく
  • カウントなどの値はコンテンツが追加されると変わるかもしれない

期待結果: JSON 出力が解析可能で期待されたキーを含む。

失敗時: JSON.parse が失敗したら、コマンドが人間テキストと JSON を混ぜているかもしれない。コマンドを修正して --json モードで純粋 JSON を出力するか、JSON サブストリングを抽出する。

ステップ7: クリーンアップと状態復元を扱う

describe('stateful commands', () => {
  const stateDir = resolve(ROOT, '.agent-almanac');

  after(() => {
    // Remove state file created by tests
    try { rmSync(stateDir, { recursive: true }); } catch {}
  });

  // Tests that create/modify state...
});

// Restore symlinks that destructive tests may remove
describe('destructive tests', () => {
  after(() => {
    // Restore symlinks that scatter/uninstall removed
    const skills = ['heal', 'meditate', 'remote-viewing'];
    for (const skill of skills) {
      const link = resolve(ROOT, `.claude/skills/${skill}`);
      if (!existsSync(link)) {
        try {
          execSync(`ln -s ../../skills/${skill} ${link}`, { cwd: ROOT });
        } catch {}
      }
    }
  });
});

状態復元ルール:

  • 状態ファイル(.agent-almanac/state.json)はテスト後にクリーンされなければならない
  • scatter/uninstall で除去された symlink は復元されなければならない
  • init で作成されたマニフェストファイル(agent-almanac.yml)は除去されなければならない
  • 順序: after() フックは宣言の逆順で実行 — 復元フックを最後に宣言する

期待結果: テストスイートはプロジェクトを見つけたのと同じ状態で残す。

失敗時: CI がテスト実行後に残ったファイルを報告したら、クリーンアップを after() に加える。漏れた状態を検出するためテスト実行後に git status を使う。

バリデーション

  • テストファイルが node --test cli/test/cli.test.js で実行する
  • すべてのテストがパス(0 失敗)
  • スモークテストが --version--help、レジストリロードをカバー
  • ライフサイクルテストがクリーンアップ付きで create → verify → delete を検証
  • 実装されたアダプタ毎に少なくとも 1 つの dry-run テストが存在
  • エラーケースがメッセージマッチで非ゼロ終了コードをテスト
  • JSON 出力テストが実出力(モックではない)を解析
  • After フックがテストで変更されたすべての状態を復元

よくある落とし穴

  • 壊れるハードコードされたカウント: レジストリ合計はコンテンツ追加で変わる。329 skills をアサートする代わりに \d+ regex を使うかカウントを動的に読む。
  • 実行順序に依存するテスト: node:test は既定で宣言順にスイートを実行するが、スイート内のテストは順序通りでないかもしれない。順序を保証するため単一 describe 内のライフサイクルスイート(create → verify → delete)を使う。
  • テスト失敗時のクリーンアップ欠落: テストがライフサイクル中に失敗しても、after() はなお実行する。しかし before() で投げると、後続テストと after() は実行しないかもしれない。before() を最小に保つ。
  • 対話プロンプトがテストをハング: 確認プロンプトを持つコマンドは execSync をハングする。echo y | をパイプするか、テストで常に --yes を渡すようにする。
  • CI で実インストールでテスト: .claude/skills/ または .agents/skills/ でファイルを作成するテストは作業ツリーを変更する。CI は "dirty working directory" チェックで失敗するかもしれない。常にクリーンアップする。

関連スキル

  • scaffold-cli-command — これらのテストが検証するコマンドを構築
  • build-cli-plugin — ステップ4 でテストされるアダプタを構築
  • design-cli-output — テストがアサートする出力パターン

GitHub 저장소

pjt222/agent-almanac
경로: i18n/ja/skills/test-cli-application
0
agentsagentskillsai-assisted-developmentclaude-codeskillsteams

연관 스킬

evaluating-llms-harness

테스팅

이 Claude Skill은 MMLU, GSM8K를 포함한 60개 이상의 표준화된 학술 과제에서 LLM 성능을 벤치마크하기 위해 lm-evaluation-harness를 실행합니다. 개발자들이 모델 품질을 비교하고, 학습 진행 상황을 추적하거나 학술 결과를 보고할 수 있도록 설계되었습니다. 이 도구는 HuggingFace와 vLLM 모델을 포함한 다양한 백엔드를 지원합니다.

스킬 보기

cloudflare-cron-triggers

테스팅

이 스킬은 cron 표현식을 사용하여 Worker를 스케줄링하기 위한 Cloudflare Cron Triggers 구현에 관한 포괄적인 지식을 제공합니다. 주기적 작업, 유지보수 작업, 자동화된 워크플로우 설정 방법을 다루며, 잘못된 cron 표현식이나 시간대 문제 같은 일반적인 이슈들을 해결하는 방법을 포함합니다. 개발자들은 이를 통해 스케줄된 핸들러 구성, cron 트리거 테스트, Workflows 및 Green Compute와의 연동 작업을 수행할 수 있습니다.

스킬 보기

webapp-testing

테스팅

이 Claude Skill은 Python 스크립트를 통해 로컬 웹 애플리케이션을 테스트하기 위한 Playwright 기반 툴킷을 제공합니다. 프론트엔드 검증, UI 디버깅, 스크린샷 캡처, 로그 확인 기능을 지원하며 서버 라이프사이클을 관리합니다. 브라우저 자동화 작업에 사용하되 컨텍스트 오염을 방지하기 위해 소스 코드를 읽지 않고 스크립트를 직접 실행하세요.

스킬 보기

finishing-a-development-branch

테스팅

이 스킬은 테스트 통과를 확인한 후 체계적인 통합 옵션을 제시하여 개발자가 완성된 작업을 마무리하도록 돕습니다. 구현이 완료된 후 머지, PR 생성, 브랜치 정리와 같은 워크플로우를 안내합니다. 코드가 준비되고 테스트가 완료되었을 때 개발 프로세스를 체계적으로 마무리하기 위해 사용하세요.

스킬 보기