コンテンツにスキップ

モックが多すぎるのはコードが設計見直しを叫んでいるサイン

概要

単体テストでモックが大量に必要になるとき、それはコードの設計に問題があることを示している。「くわラジ」#2 でモック地獄から始まり、サイクロマティック複雑度・技術的負債の話まで展開したポッドキャスト。

詳細

「モック地獄」とは

// モックが多すぎるテストの例
func TestOrderService_PlaceOrder(t *testing.T) {
    mockDB := NewMockDatabase()
    mockEmail := NewMockEmailSender()
    mockInventory := NewMockInventoryService()
    mockPayment := NewMockPaymentGateway()
    mockLogger := NewMockLogger()
    mockMetrics := NewMockMetricsCollector()
    mockCache := NewMockCache()
    // まだ続く...

    svc := NewOrderService(mockDB, mockEmail, mockInventory, 
                           mockPayment, mockLogger, mockMetrics, mockCache)
    svc.PlaceOrder(...)
}

モックが7個も必要になっている → このクラスは依存が多すぎる = 責務が多すぎる

なぜモックが多いと設計が悪いのか

モックが必要な理由 = 外部に依存しているから
依存が多い = 関心事が多い = 単一責任の原則違反

単一責任の原則:
  「クラスは変更される理由が1つだけであるべき」
  → 依存先が多いということは変更理由が多いということ

設計の問題を見つけるシグナル

シグナル 意味
テストのセットアップが長い クラスの依存が多い
全てのメソッドで同じモックが必要 依存を注入する場所が間違っている
モックの動作設定が複雑 ロジックがサービス層に漏れている
モックを変えるとテストが大量に落ちる 抽象化が薄い(実装に依存しすぎ)

サイクロマティック複雑度との関係

サイクロマティック複雑度 = コード内の独立した実行パスの数
  if 1つ = +1
  for 1つ = +1
  case 1つ = +1

複雑度が高い = テストケースが多い = モックも多くなりがち

目安:
  1〜5: シンプル
  6〜10: やや複雑、注意
  11〜: 複雑すぎ、リファクタリングを検討

解決アプローチ

1. 依存を減らす(責務を分割する)

// Before: OrderService が全てを知っている
type OrderService struct {
    db        Database
    email     EmailSender
    inventory InventoryService
    payment   PaymentGateway
    // ...
}

// After: 各責務を小さなサービスに分割
type OrderPlacer struct {
    inventory InventoryChecker  // この責務だけ
    payment   PaymentProcessor  // この責務だけ
}

type OrderNotifier struct {
    email EmailSender  // 通知責務だけ
}

2. ポートとアダプターパターン(Hexagonal Architecture)

// ポート(インターフェース)だけに依存する
type OrderRepository interface {
    Save(order Order) error
    FindByID(id string) (Order, error)
}

// 実装(アダプター)はテスト時にインメモリで差し替え可能
type InMemoryOrderRepository struct {
    orders map[string]Order
}

3. 統合テストで補う

全てを単体テストしようとするとモックだらけになる。 依存先が多い部分はむしろ 統合テスト に任せる。

単体テスト: 純粋なビジネスロジック(副作用なし)
統合テスト: DB・外部サービスとの連携
E2Eテスト: ユーザーシナリオ全体

技術的負債との関係

モック地獄は技術的負債が可視化されたもの。 テストを書くことで「設計の問題」が浮き彫りになる → テストは設計のフィードバックツール

テストが書きにくい = 設計に問題がある可能性が高い
テストが書きやすい = 依存が少なく、責務が明確

なぜ重要か / いつ使うか

  • テストのセットアップに時間がかかりすぎるとき
  • 新機能を追加するたびに既存テストが大量に落ちるとき
  • 「テストを書けと言われるがどう書けばいいか」と感じるとき