test-antipatterns について
実際の振る舞いを検証し、モックは分離に限って使うことを重視するテスト向けスキルです。モックに関する典型的な問題と、インテグレーションテストを検討する場面を扱います。
主な機能
- 01モックでなく実際の振る舞いをテスト
- 02テスト専用メソッドの本番コードへの追加を回避
- 03モック前に依存関係を把握
- 04モックデータの構造を完全に再現
- 05実装と並行してテストを作成
活用シーン
- 01モック要素を対象とするアサーションの確認
- 02メソッドをテストユーティリティに移す判断
- 03モックの副作用によるテスト問題の調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ pedronauck/ skills --skill test-antipatternsインストール後、Claude Code で/test-antipatternsを入力して読み込まれたか確認します。
詳細
テストにおけるアンチパターン
概要
テストではモックの動作ではなく、実際の動作を検証する必要があります。モックは分離するための手段であり、テスト対象ではありません。
基本原則: モックの動作ではなく、コードの動作をテストします。
これらの原則を一貫して適用することで、次のアンチパターンを防げます。
鉄則
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"
これが誤りである理由:
- テストは実装の一部であり、任意の後続作業ではありません
- テストがなければ、完了したとは言えません
- 実装と並行してテストを書くことで、問題を早期に発見できます
修正方法:
Tests are part of implementation:
1. Write tests alongside implementation
2. Verify behavior works correctly
3. Refactor as needed
4. THEN claim complete
モックが複雑になりすぎた場合
警告サイン:
- モックのセットアップがテストのロジックより長い
- テストを成功させるために、あらゆるものをモックしている
- 実際のコンポーネントにあるメソッドがモックにない
- モックを変更するとテストが壊れる
人間のパートナーからの質問:「ここでモックを使う必要がありますか?」
検討事項: 実際のコンポーネントを使うインテグレーションテストのほうが、複雑なモックよりシンプルな場合があります
クイックリファレンス
| アンチパターン | 修正方法 |
|---|---|
| モックの要素をアサートする | 実際のコンポーネントをテストするか、モックを解除する |
| 本番コードにテスト専用メソッドを追加する | テストユーティリティに移す |
| 理解せずにモックする | まず依存関係を理解し、必要最小限のモックを使う |
| 不完全なモック | 実際の API を完全に再現する |
| テストを後回しにする | テストは実装の一部 |
| 複雑すぎるモック | インテグレーションテストを検討する |
危険信号
- アサーションが
*-mockテスト ID を確認している - テストファイルでしか呼び出されないメソッドがある
- テストの 50% を超える部分がモックのセットアップで占められている
- モックを取り除くとテストが失敗する
- モックが必要な理由を説明できない
- 「安全のためだけ」にモックしている
要点
モックは分離するためのツールであり、テスト対象ではありません。
モックの動作をテストしているなら、進め方を誤っています。
修正方法:実際の動作をテストするか、そもそもなぜモックするのかを見直してください。
コメント
Markdown 対応