test-cli-application
О программе
Этот навык предоставляет шаблоны для написания интеграционных тестов для CLI-приложений на Node.js с использованием встроенного модуля `node:test`. Он охватывает тестирование вывода CLI, состояния файловой системы, ошибочных сценариев и процедур очистки. Используйте его при добавлении тестов к существующим CLI, проверке новых команд или настройке CI для CLI-инструментов.
Быстрая установка
Claude Code
Рекомендуетсяnpx skills add pjt222/agent-almanac -a claude-code/plugin add https://github.com/pjt222/agent-almanacgit clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/test-cli-applicationСкопируйте и вставьте эту команду в Claude Code для установки этого навыка
Документация
Test CLI Application
Write integration tests for Node.js CLI using built-in node:test module with execSync.
When Use
- Add tests to existing CLI application
- Test newly created command
- Verify adapter/plugin behavior across target frameworks
- Set up CI that validates CLI correctness
- Catch regressions after refactoring CLI internals
Inputs
- Required: Path to CLI entry point (e.g.,
cli/index.js) - Required: Commands to test
- Optional: Framework adapters to test (dry-run mode)
- Optional: Cleanup requirements (files/symlinks created by tests)
Steps
Step 1: Set Up Test Infrastructure
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,
});
}
Key design decisions:
node:testis built-in — no test runner dependency neededexecSyncruns the CLI as a subprocess — tests the actual binary, not internal functions- 10-second timeout prevents hanging on interactive prompts
encoding: 'utf8'gives string output for regex matching- All paths relative to
ROOTfor reproducibility
Got: Test file imports from node:test, has working run() helper.
If fail: node:test not available? Node.js version below 18. Upgrade or use polyfill.
Step 2: Write Smoke Tests
Smoke tests verify CLI starts, parses arguments, produces expected output shapes:
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/);
});
});
Smoke test patterns:
--versionand--helpalways work- Registry loading validates data integrity
- Search with known and unknown terms
Got: Smoke tests confirm CLI functional and data loaded.
If fail: Registry counts change frequent? Use \d+ instead of hardcoded numbers.
Step 3: Write Lifecycle Tests
Lifecycle tests verify create → verify → delete sequences with cleanup:
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));
});
});
Cleanup rules:
- Use
after()hooks, notafterEach()— lifecycle tests build on each other - Wrap cleanup in
try/catch— cleanup must not fail the test suite - Clean from leaf to root (file → parent dir → grandparent dir)
- If the test modifies shared state (symlinks, config files), restore it
Got: Tests run in sequence within describe block, cleanup runs even on failure.
If fail: Tests run in parallel (non-default in node:test)? Force sequential with { concurrency: 1 }.
Step 4: Write Dry-Run Tests for Each Adapter
Test each adapter's target path without making changes:
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);
});
});
This pattern scales to any number of adapters. Each test:
- Uses
--frameworkto bypass auto-detection - Uses
--dry-runso no files are created - Asserts the target path appears in output
Got: One describe block per adapter, each with at least path assertion.
If fail: Adapter doesn't exist in project? Test will fail with "Unknown framework." Correct — adapter tests should only exist for implemented adapters.
Step 5: Write Error Case Tests
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/,
);
});
});
Error testing patterns:
assert.throwscatches non-zero exit codes fromexecSync- Regex match on the error message (captured from stderr)
- Test both "item not found" and "invalid option" errors
- Verify error messages suggest corrective actions
Got: All error paths produce non-zero exit codes and helpful messages.
If fail: execSync throws on non-zero exit. Error's stderr or stdout contains message. Check error.stdout if assert.throws regex doesn't match.
Step 6: Write JSON Output Tests
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 testing gotchas:
- Some commands prefix JSON with human-readable text (e.g., DRY RUN header)
- Extract JSON by finding the first
{character - Validate structure (key presence, types), not exact values
- Values like counts may change as content is added
Got: JSON output parseable, contains expected keys.
If fail: JSON.parse fails? Command may be mixing human text with JSON. Either fix command to output pure JSON in --json mode, or extract JSON substring.
Step 7: Handle Cleanup and State Restoration
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 {}
}
}
});
});
State restoration rules:
- State files (
.agent-almanac/state.json) must be cleaned after tests - Symlinks removed by
scatter/uninstallmust be restored - Manifest files (
agent-almanac.yml) created byinitmust be removed - Order:
after()hooks run in reverse declaration order — declare restore hooks last
Got: Test suite leaves project in same state it found it.
If fail: CI reports leftover files after test runs? Add cleanup to after(). Use git status after test runs to detect leaked state.
Checks
- Test file runs with
node --test cli/test/cli.test.js - All tests pass (0 failures)
- Smoke tests cover
--version,--help, registry loading - Lifecycle tests verify create → verify → delete with cleanup
- At least one adapter dry-run test exists per implemented adapter
- Error cases test non-zero exit codes with message matching
- JSON output tests parse actual output (not mocked)
- After hooks restore all state modified by tests
Pitfalls
- Hardcoded counts that break: Registry totals change as content added. Use
\d+regex or read count dynamic instead of asserting329 skills. - Tests depend on execution order:
node:testruns suites in declaration order default, but tests within suite may not. Use lifecycle suites (create → verify → delete) within singledescribeto guarantee order. - Missing cleanup on test failure: Test fails mid-lifecycle,
after()still runs. But throw inbefore()? Subsequent tests andafter()may not run. Keepbefore()minimal. - Interactive prompts hang tests: Commands with confirmation prompts will hang
execSync. Either pipeecho y |or ensure--yesalways passed in tests. - Test with real installs in CI: Tests that create files in
.claude/skills/or.agents/skills/modify working tree. CI may fail on "dirty working directory" checks. Always clean up.
See Also
scaffold-cli-command— build commands that these tests verifybuild-cli-plugin— build adapters tested in Step 4design-cli-output— output patterns that tests assert against
GitHub репозиторий
Frequently asked questions
What is the test-cli-application skill?
test-cli-application is a Claude Skill by pjt222. Skills package instructions and resources that Claude loads on demand, so Claude can perform test-cli-application-related tasks without extra prompting.
How do I install test-cli-application?
Use the install commands on this page: add test-cli-application to Claude Code as a plugin, or clone its repository into your skills directory, then restart Claude so it picks up the skill.
What category does test-cli-application belong to?
test-cli-application is in the Testing category, tagged testing, api and design.
Is test-cli-application free to use?
Yes. test-cli-application is listed on AIMCP and free to install. It runs inside Claude, so no separate service account is required to use the skill itself.
Похожие навыки
Этот навык Claude запускает lm-evaluation-harness для тестирования LLM на более чем 60 стандартизированных академических задачах, таких как MMLU и GSM8K. Он предназначен для разработчиков, чтобы сравнивать качество моделей, отслеживать прогресс обучения или сообщать академические результаты. Инструмент поддерживает различные бэкенды, включая модели HuggingFace и vLLM.
Этот навык предоставляет обширные знания по реализации Cloudflare Cron Triggers для планирования запуска Workers с помощью cron-выражений. Он охватывает настройку периодических задач, заданий технического обслуживания и автоматизированных рабочих процессов, а также решение распространенных проблем, таких как неверные cron-выражения и ошибки часовых поясов. Разработчики могут использовать его для настройки планировщиков обработчиков, тестирования cron-триггеров и интеграции с Workflows и Green Compute.
Этот навык Claude предоставляет инструментарий на базе Playwright для тестирования локальных веб-приложений с помощью Python-скриптов. Он позволяет проводить проверку фронтенда, отладку интерфейса, создание скриншотов и просмотр логов, одновременно управляя жизненным циклом сервера. Используйте его для задач автоматизации браузера, но запускайте скрипты напрямую, вместо чтения их исходного кода, чтобы избежать загрязнения контекста.
Этот навык помогает разработчикам завершать готовую работу, проверяя прохождение тестов и предлагая структурированные варианты интеграции. Он направляет рабочий процесс по слиянию, созданию пул-реквестов или очистке веток после завершения реализации. Используйте его, когда ваш код готов и протестирован, чтобы систематически завершать процесс разработки.
