mcpField
T
testing-anti-patterns

testing-anti-patterns

github.com/jackspaceテスト・品質1636 回インストール12 週間前に更新
リポジトリ

testing-anti-patterns について

testing-anti-patternsは、ソフトウェアテストのアンチパターンを扱うスキルです。テストを作成・保守する開発者向けに、実際の動作の検証、慎重なモック利用、TDDや統合テストを説明します。

主な機能

  1. 01モック自体へのアサーションを避ける
  2. 02本番クラスにテスト専用メソッドを加えない
  3. 03依存関係を理解してからモックを使う
  4. 04モックデータを実際の構造に合わせる
  5. 05実装工程にテストを組み込む
  6. 06複雑なモックのリスクを把握する

活用シーン

  1. 01テストが実際のコンポーネント動作を検証するか確認
  2. 02過度なモックに依存するテストのリファクタリング
  3. 03実装前にTDDのテスト工程を確立

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jackspace/claudeskillz --skill testing-anti-patterns

インストール後、Claude Code で/testing-anti-patternsを入力して読み込まれたか確認します。

同梱ファイルSKILL.json

詳細

テストのアンチパターン

概要

テストではモックの動作ではなく、実際の動作を検証する必要があります。モックは分離するための手段であり、テスト対象ではありません。

基本原則: モックの動作ではなく、コードの動作をテストします。

厳格な 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 が役立つ理由:

  1. 先にテストを書く → 実際に何をテストしているのか考えるようになります
  2. 失敗するのを確認する → モックではなく実際の動作をテストしていると確認できます
  3. 最小限の実装にする → テスト専用メソッドが紛れ込まなくなります
  4. 実際の依存関係を使う → モックする前に、テストに本当に必要なものを把握できます

モックの動作をテストしているなら、TDD に違反しています ― 実際のコードに対してテストが失敗するのを確認せずに、モックを追加したということです。

クイックリファレンス

アンチパターン修正方法
モック要素をアサートする実際のコンポーネントをテストするか、モックを外す
本番コードにテスト専用メソッドを追加するテスト用ユーティリティに移す
理解せずにモックするまず依存関係を理解し、最小限だけモックする
不完全なモック実際の API を完全に再現する
テストを後回しにするTDD ― 先にテストを書く
過度に複雑なモック統合テストを検討する

危険信号

  • アサーションが *-mock のテスト ID を確認している
  • テストファイルでしか呼び出されないメソッドがある
  • モックのセットアップがテストの 50% を超えている
  • モックを削除するとテストが失敗する
  • モックが必要な理由を説明できない
  • 「念のため」にモックしている

要点

モックは分離するためのツールであり、テスト対象ではありません。

TDD によってモックの動作をテストしていると分かったなら、進め方を誤っています。

修正方法:実際の動作をテストするか、そもそもモックが必要なのかを問い直してください。

コメント