Testing Anti-Patterns について
Testing Anti-Patterns は、テストを書く開発者向けのエージェントスキルで、モックではなく実際の動作を検証することを重視します。モックの使い方、本番コードへのテスト専用メソッド、不完全なテストデータ、TDDへのテスト統合を扱います。
主な機能
- 01モックではなく実際の動作を検証
- 02本番コードのテスト専用メソッドを指摘
- 03理解に基づく最小限のモックを案内
- 04完全なモックデータ構造を要求
- 05テストをTDDに組み込む
活用シーン
- 01モック要素へのアサーションの見直し
- 02モックの要否と範囲の判断
- 03実際のスキーマに照らしたモック応答の確認
- 04TDDサイクルによる回帰テストの計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ obra/ superpowers-skills --skill testing-anti-patternsインストール後、Claude Code で/testing-anti-patternsを入力して読み込まれたか確認します。
詳細
テストのアンチパターン
概要
テストではモックの動作ではなく、実際の動作を検証する必要があります。モックは分離するための手段であり、テスト対象ではありません。
基本原則: モックの動作ではなく、コードの動作をテストします。
厳格な TDD に従えば、こうしたアンチパターンを防げます。
鉄則
1. NEVER test mock behavior
2. NEVER add test-only methods to production classes
3. NEVER mock without understanding dependencies
アンチパターン 1:モックの動作をテストする
違反例:
// ❌ BAD: Testing that the mock exists
test('renders sidebar', () => {
render(<Page />);
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
});
これが誤りである理由:
- コンポーネントではなく、モックが機能することを検証しています
- モックがある場合はテストに合格し、ない場合は失敗します
- 実際の動作について何も分かりません
人間のパートナーからの指摘:「モックの動作をテストしていませんか?」
修正方法:
// ✅ GOOD: Test real component or don't mock it
test('renders sidebar', () => {
render(<Page />); // Don't mock sidebar
expect(screen.getByRole('navigation')).toBeInTheDocument();
});
// OR if sidebar must be mocked for isolation:
// Don't assert on the mock - test Page's behavior with sidebar present
ゲート関数
BEFORE asserting on any mock element:
Ask: "Am I testing real component behavior or just mock existence?"
IF testing mock existence:
STOP - Delete the assertion or unmock the component
Test real behavior instead
アンチパターン 2:本番コードにテスト専用メソッドを追加する
違反例:
// ❌ BAD: destroy() only used in tests
class Session {
async destroy() { // Looks like production API!
await this._workspaceManager?.destroyWorkspace(this.id);
// ... cleanup
}
}
// In tests
afterEach(() => session.destroy());
これが誤りである理由:
- 本番クラスがテスト専用コードで汚染されます
- 本番環境で誤って呼び出された場合に危険です
- YAGNI と関心の分離に違反します
- オブジェクトのライフサイクルとエンティティのライフサイクルを混同させます
修正方法:
// ✅ GOOD: Test utilities handle test cleanup
// Session has no destroy() - it's stateless in production
// In test-utils/
export async function cleanupSession(session: Session) {
const workspace = session.getWorkspaceInfo();
if (workspace) {
await workspaceManager.destroyWorkspace(workspace.id);
}
}
// In tests
afterEach(() => cleanupSession(session));
ゲート関数
BEFORE adding any method to production class:
Ask: "Is this only used by tests?"
IF yes:
STOP - Don't add it
Put it in test utilities instead
Ask: "Does this class own this resource's lifecycle?"
IF no:
STOP - Wrong class for this method
アンチパターン 3:理解せずにモックを使う
違反例:
// ❌ BAD: Mock breaks test logic
test('detects duplicate server', () => {
// Mock prevents config write that test depends on!
vi.mock('ToolCatalog', () => ({
discoverAndCacheTools: vi.fn().mockResolvedValue(undefined)
}));
await addServer(config);
await addServer(config); // Should throw - but won't!
});
これが誤りである理由:
- モックしたメソッドには、テストが依存していた副作用(設定の書き込み)がありました
- 「念のため」と過剰にモックすると、実際の動作が壊れます
- テストが誤った理由で合格したり、原因不明の失敗をしたりします
修正方法:
// ✅ GOOD: Mock at correct level
test('detects duplicate server', () => {
// Mock the slow part, preserve behavior test needs
vi.mock('MCPServerManager'); // Just mock slow server startup
await addServer(config); // Config written
await addServer(config); // Duplicate detected ✓
});
ゲート関数
BEFORE mocking any method:
STOP - Don't mock yet
1. Ask: "What side effects does the real method have?"
2. Ask: "Does this test depend on any of those side effects?"
3. Ask: "Do I fully understand what this test needs?"
IF depends on side effects:
Mock at lower level (the actual slow/external operation)
OR use test doubles that preserve necessary behavior
NOT the high-level method the test depends on
IF unsure what test depends on:
Run test with real implementation FIRST
Observe what actually needs to happen
THEN add minimal mocking at the right level
Red flags:
- "I'll mock this to be safe"
- "This might be slow, better mock it"
- Mocking without understanding the dependency chain
アンチパターン 4:不完全なモック
違反例:
// ❌ BAD: Partial mock - only fields you think you need
const mockResponse = {
status: 'success',
data: { userId: '123', name: 'Alice' }
// Missing: metadata that downstream code uses
};
// Later: breaks when code accesses response.metadata.requestId
これが誤りである理由:
- 部分的なモックは構造上の前提を隠します ― 把握しているフィールドだけをモックしています
- 後続のコードが、含めていないフィールドに依存する可能性があります ― 気付かれないまま失敗します
- テストには合格しても、統合時に失敗します ― モックは不完全でも、実際の API は完全です
- 誤った安心感を与えます ― テストは実際の動作について何も証明しません
鉄則: 直近のテストで使うフィールドだけでなく、実際に存在する完全なデータ構造をモックしてください。
修正方法:
// ✅ GOOD: Mirror real API completeness
const mockResponse = {
status: 'success',
data: { userId: '123', name: 'Alice' },
metadata: { requestId: 'req-789', timestamp: 1234567890 }
// All fields real API returns
};
ゲート関数
BEFORE creating mock responses:
Check: "What fields does the real API response contain?"
Actions:
1. Examine actual API response from docs/examples
2. Include ALL fields system might consume downstream
3. Verify mock matches real response schema completely
Critical:
If you're creating a mock, you must understand the ENTIRE structure
Partial mocks fail silently when code depends on omitted fields
If uncertain: Include all documented fields
アンチパターン 5:統合テストを後回しにする
違反例:
✅ Implementation complete
❌ No tests written
"Ready for testing"
これが誤りである理由:
- テストは実装の一部であり、任意の追加作業ではありません
- TDD ならこれを検出できたはずです
- テストなしでは完了したと言えません
修正方法:
TDD cycle:
1. Write failing test
2. Implement to pass
3. Refactor
4. THEN claim complete
モックが複雑になりすぎた場合
警告サイン:
- モックのセットアップがテストロジックより長い
- テストに合格させるためにあらゆるものをモックしている
- 実際のコンポーネントにあるメソッドがモックにない
- モックを変更するとテストが壊れる
人間のパートナーからの質問:「ここでモックを使う必要はありますか?」
検討事項: 実際のコンポーネントを使う統合テストのほうが、複雑なモックよりシンプルな場合がよくあります
TDD で防げるアンチパターン
TDD が役立つ理由:
- 先にテストを書く → 実際に何をテストしているのか考えるようになります
- 失敗するのを確認する → モックではなく実際の動作をテストしていると確認できます
- 最小限の実装にする → テスト専用メソッドが紛れ込まなくなります
- 実際の依存関係を使う → モックする前に、テストに本当に必要なものを把握できます
モックの動作をテストしているなら、TDD に違反しています ― 実際のコードに対してテストが失敗するのを確認せずに、モックを追加したということです。
クイックリファレンス
| アンチパターン | 修正方法 |
|---|---|
| モック要素をアサートする | 実際のコンポーネントをテストするか、モックを外す |
| 本番コードにテスト専用メソッドを追加する | テスト用ユーティリティに移す |
| 理解せずにモックする | まず依存関係を理解し、最小限だけモックする |
| 不完全なモック | 実際の API を完全に再現する |
| テストを後回しにする | TDD ― 先にテストを書く |
| 過度に複雑なモック | 統合テストを検討する |
危険信号
- アサーションが
*-mockのテスト ID を確認している - テストファイルでしか呼び出されないメソッドがある
- モックのセットアップがテストの 50% を超えている
- モックを削除するとテストが失敗する
- モックが必要な理由を説明できない
- 「念のため」にモックしている
要点
モックは分離するためのツールであり、テスト対象ではありません。
TDD によってモックの動作をテストしていると分かったなら、進め方を誤っています。
修正方法:実際の動作をテストするか、そもそもモックが必要なのかを問い直してください。