testing について
TypeScript テストの構成、アサーションの命名、テストとベンチマークの分離に関する規約をまとめたスキルです。型テストやセットアップ、既存テストの監査・整理の指針も示します。
主な機能
- 01Result アサーションヘルパー
- 02テストとベンチマークの分離
- 03テストファイルの文書化規則
- 04挙動ごとのテストファイル構成
- 05挙動を示すテスト名
活用シーン
- 01wellcrafted Result のテスト作成
- 02性能レポートと CI テストの分離
- 03異なる挙動ごとのファイル分割
- 04既存テストの監査や整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ epicenterhq/ epicenter --skill testingインストール後、Claude Code で/testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/honest-tests.mdsetup-pattern.mdtest-deletion-grill.mdtest-structure.mdtype-testing.md詳細
テストファイルの規約
参考資料
作業内容に応じて、必要な資料を参照してください。
- ネガティブ型テスト(
@ts-expect-error、bun:testの型テスト戦略、as anyを使わないルール)に取り組む場合は、references/type-testing.md を読んでください。 - テストセットアップの設計(
setup()のパターン、合成可能なセットアップ、beforeEachの回避、共有スキーマ)に取り組む場合は、references/setup-pattern.md を読んでください。 - テストの構成(フラットなテスト、
describe()の境界、ネストよりヘルパーを優先する方針)に取り組む場合は、references/test-structure.md を読んでください。 - 既存テストの監査で、曖昧なアサーション、素通しのゲッター、停止したフェイク、不要なフェイクのインターフェース、コードと矛盾するドキュメントコメントを確認する場合は、references/honest-tests.md を読んでください。
- 削除しても問題ないか判断が必要なテストを削除または整理する場合は、references/test-deletion-grill.md を読んでください。
外部資料:
- Kent C. Dodds, "Avoid Nesting When You're Testing" :
beforeEachよりセットアップ関数を使うこと、フラットなテスト - Kent C. Dodds, "AHA Testing" :テストで性急な抽象化を避けること
- Kent C. Dodds, Testing JavaScript :テストオブジェクトファクトリーパターン
- Matt Pocock, "How to test your types" :型テストにおける Vitest の
expectTypeOf - Matt Pocock,
shoehorn:テストを使いやすくするための部分モック
関連スキル:テスト対象のサービスパターンについては
services-layerを参照してください。型テストの規約についてはtypescriptを参照してください。
結果のアサーション
テストで適切に構築された Result をアサートする場合は、独自にエラーチェックを実装するのではなく、wellcrafted/testing の expectOk と expectErr を使用してください。
import { expectErr, expectOk } from 'wellcrafted/testing';
const data = expectOk(await service.doThing());
expect(data.id).toBe('1');
const error = expectErr(await service.doThing({ invalid: true }));
expect(error.name).toBe('InvalidInput');
値が wellcrafted の Result である場合、ローカルヘルパーの複製、成功確認としての expect(error).toBeNull()、および値を取り出すための if (error) throw ... は避けてください。
このルールは、通常のレスポンスボディ、UI スナップショット、または単に error プロパティを持つその他のオブジェクトには適用されません。
テストとベンチマーク
拡張子が異なれば、目的も異なります。
*.test.ts:expect()で動作をアサートします。bun test(リポジトリのデフォルト、CI)で実行されます。expect()の呼び出しが1つもないテストファイルは、この拡張子を使うべきではありません。*.bench.ts:測定して結果を報告します。表、実行時間、またはストレージサイズを出力します。bun benchでのみ実行されます。アサーションは不要です(共有ハードウェアではパフォーマンスのしきい値が不安定になるため、視覚的な傾向を優先してください)。
1つのファイルが両方に該当することはありません。ベンチマークはパッケージ内の src/__benchmarks__/ に配置し、テストは対象モジュールと同じ場所に配置します。bun test のデフォルト検出グロブが対象とするのは *.test.ts などのファイルのみです。そのため、レポートを .test.ts から .bench.ts に変更すれば、CI の対象から除外されます。
ファイルレベルのドキュメントコメント
すべての .test.ts ファイルは、テスト対象と検証する主要な動作を説明する JSDoc ブロックで始める必要があります。これはモジュールの契約を説明するドキュメントとして機能します。
構成
/**
* [Module Name] Tests
*
* [1-3 sentences explaining what this file tests and why these tests matter.]
*
* Key behaviors:
* - [Behavior 1]
* - [Behavior 2]
* - [Behavior 3]
*
* See also:
* - `related-file.test.ts` for [related aspect]
*/
良い例
/**
* Cell-Level LWW CRDT Sync Tests
*
* Verifies cell-level LWW conflict resolution where each field
* has its own timestamp. Unlike row-level LWW, concurrent edits to
* DIFFERENT fields merge independently.
*
* Key behaviors:
* - Concurrent edits to SAME field: latest timestamp wins
* - Concurrent edits to DIFFERENT fields: BOTH preserved (merge)
* - Delete removes all cells for a row
*/
悪い例(簡素すぎる)
// Tests for create-tables
セクション見出し
長いテストファイル(100行以上)では、コメント見出しを使って論理的なセクションを区切ってください。
// ============================================================================
// MESSAGE_SYNC Tests
// ============================================================================
複数の側面を持つテストファイルの分割
モジュールに動作上の側面が複数ある場合は、単一の巨大なファイルにまとめず、目的に絞ったテストファイルに分割してください。
| パターン | 用途 |
|---|---|
{module}.test.ts | コアとなる CRUD の動作、正常系、境界ケース |
{module}.types.test.ts | 型推論の検証、ネガティブ型テスト |
{module}.{scenario}.test.ts | 特定のシナリオ(CRDT 同期、オフライン、統合) |
分割する場合
- ファイルが約 500 行を超える場合
- テストが明確に異なる関心事(CRUD、同期、型など)を対象としている場合
- 関心事ごとに異なるセットアップ要件がある場合
分割しない場合
- 分割すると、テストが 3 件未満のファイルができる場合
- すべてのテストで同じセットアップと関心事を共有している場合
テストの命名
テストの説明は、曖昧な説明ではなく、動作を検証するものでなければなりません。テストが失敗したときに、何が壊れたのかが名前からわかるようにしてください。
ルール
- 「正常に動作する」ではなく、何が起きるかを示す
- エッジケースをテストする場合は、条件を含める
- 埋め草の言葉を使わない:「should」、「correctly」、「properly」を加えても意味はありません
良い名前
test('upsert stores row and get retrieves it', () => { ... });
test('filter returns only published posts', () => { ... });
test('concurrent edits to different fields: both preserved', () => { ... });
test('delete vs update race: update wins (rightmost entry)', () => { ... });
test('observer fires once per transaction, not per operation', () => { ... });
test('get() throws for undefined tables with helpful message', () => { ... });
悪い名前
test('should work correctly', () => { ... }); // What works? What's correct?
test('should handle batch operations', () => { ... }); // Handle how?
test('basic test', () => { ... }); // Says nothing
test('should create and retrieve rows correctly', () => { ... }); // Vague "correctly"
パターン:{action} {outcome} [condition]
"upsert stores row and get retrieves it"
^^^^^^ ^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^
action outcome action outcome
"observer fires once per transaction, not per operation"
^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
subject outcome condition
"get() returns not_found for non-existent rows"
^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
action outcome condition