モックが辛いのはコードの悲鳴 — テスト設計と技術的負債
概要¶
単体テストでモックが大量に必要になるのは「コードが設計を見直せと叫んでいるサイン」という話をポッドキャスト「くわラジ」#2 で解説。サイクロマティック複雑度や技術的負債の話まで広がっている。
ポッドキャスト: https://kuwa-raji.henteko07.com
詳細¶
モックが増える根本原因¶
// BAD: 依存が多くモックだらけのテスト
func (s *UserService) CreateUser(name string) error {
user := User{Name: name}
s.db.Save(user) // → DB モック必要
s.email.Send(user) // → メールモック必要
s.analytics.Track(user) // → 分析モック必要
s.cache.Invalidate(user) // → キャッシュモック必要
return nil
}
上記のテストには 4 つのモックが必要。これはコードが「単一責任原則を破っている」というサイン。
// GOOD: 依存を分離するとモックが減る
func (s *UserService) CreateUser(name string) (User, error) {
return User{Name: name}, nil // 純粋関数 → モック不要
}
// 副作用は呼び出し側で組み合わせる
user, _ := service.CreateUser(name)
db.Save(user)
email.Send(user)
サイクロマティック複雑度とモック¶
サイクロマティック複雑度(分岐の数)が高いコードほど、テストで網羅すべきパスが増え、モックも増える。
Go での計測ツール: gocyclo, golangci-lint
技術的負債との関係¶
モック地獄は技術的負債の症状の一つ: - 密結合 → モック多い → テスト維持コスト増 - テスト維持コスト増 → テストを書かなくなる → 品質低下 - 品質低下 → バグ修正に時間 → 新機能開発が遅れる
処方箋¶
- 依存性注入(DI)を徹底する — 外部依存をコンストラクタで受け取る
- 副作用を境界に押し出す — ドメインロジックを純粋関数に近づける
- インターフェースを薄くする — 必要な操作だけのインターフェースを作る
- 統合テストとのバランス — 単体テストでモックするより、実際の DB で統合テストする方が安いケースもある
なぜ重要か / いつ使うか¶
- 「テストが書きにくい」と感じたとき、まずコード設計を疑う
- コードレビューで「モック多すぎ」をリファクタリングの提案根拠にする
- 技術的負債の優先度付けに「テストの複雑さ」を指標として使う