モックと依存注入のレビュー:過剰抽象か適切分離かをどう判断するか
モックと依存注入のレビュー:過剰抽象か適切分離かをどう判断するか
Go言語では、テスト容易性を高めるためにinterfaceによる抽象化とモック化が頻繁に行われる。
しかしレビューの現場では、その抽象が本当に必要か?、または抽象過剰による複雑化を招いていないか?という観点が重要になる。
1. モックとDIの目的整理
モックとDIとは
モックとは、実装を差し替えるためのスタブ(擬似実装)
DI(依存注入)とは、構造体の内部で依存先を直接生成せず、外部から注入して責務を分離する設計
主な目的:
- テスト容易性の向上(単体テストを外部依存なく実行)
- 実装の柔軟性(後から差し替えや拡張がしやすい)
- 責務分離(呼び出す側は「何をするか」だけに集中)
2. 典型的なモック化構造
interfaceによる依存抽象
type MailSender interface {
SendMail(to, subject, body string) error
}
type RealMailSender struct{}
func (r *RealMailSender) SendMail(to, subject, body string) error {
// SMTP送信処理
return nil
}
type UserService struct {
sender MailSender
}
func (s *UserService) NotifyUser(email string) error {
return s.sender.SendMail(email, "Hello", "Welcome!")
}
Comment
@Reviewer: `UserService` は `MailSender` に依存しているが、直接の型ではなくインターフェースで抽象化されているため、テスト時には差し替え可能な設計。
モックを使って `NotifyUser()` の動作だけを検証可能になっている点を評価する。
3. モックが不要な場面での“抽象の過剰”
以下は過剰抽象の典型パターン:
過剰なinterface分離
type TimeProvider interface {
Now() time.Time
}
Comment
@Reviewer: `time.Now()` という組み込みの静的関数をラップするだけのインターフェースは過剰抽象の典型。
このラップによりテスト可能性は得られるが、コード全体の冗長さ・読解難度・初見の理解コストを高める。
頻繁に変化しない、かつ単純な依存(日時、UUIDなど)は直接使用を原則とし、例外を設計意図と共に明示させる。
4. 適切なDIと過剰抽象の構造比較

Comment
@Reviewer: この構造ではUserServiceのテストがMockMailSenderで完結可能。
設計の抽象は「将来変わる可能性がある依存」だけに限定すべきで、恒久的な依存は具象型でよいケースもある。
5. レビュー観点:抽象の妥当性
観点 | 確認事項 |
---|---|
責務分離 | インターフェース化は呼び出し側の責務単純化に寄与しているか? |
実装の切り替え | 将来の差し替えや条件分岐での柔軟性が想定されているか? |
テスト目的 | モック化によってテストの粒度が適切に絞られているか? |
過剰抽象 | ラップだけの無意味なinterfaceではないか?実装が1種類しかないならinterface不要では? |
実体の注入元 | DIの注入方法(構造体の初期化、Factory、Injectorなど)が妥当か? |
6. 適切なモック実装とテスト
モック構造のテスト使用例
type MockMailSender struct {
Sent bool
}
func (m *MockMailSender) SendMail(to, subject, body string) error {
m.Sent = true
return nil
}
func TestNotifyUser(t *testing.T) {
mock := &MockMailSender{}
service := UserService{sender: mock}
_ = service.NotifyUser("[email protected]")
if !mock.Sent {
t.Errorf("メール送信が呼び出されていません")
}
}
Comment
@Reviewer: このように、モックは「副作用が発生したか」の確認が主目的。
一方、戻り値・副作用が無いメソッドに対するモックは意義が薄く、テスト実装コストが上回る場合も多い。
7. 適切な設計判断の指摘例
- type UUIDProvider interface {
- NewUUID() string
- }
+ // 組み込みuuid.New().String() で十分
Comment
@Reviewer: UUIDのように非可変かつ意味が固定された生成関数に対し、interfaceを挟む意義は薄い。
変更頻度や切り替えニーズが将来的に見込まれないなら、具象関数をそのまま使う方がシンプルかつ明快。
8. まとめ:抽象の「目的」と「将来性」が見えているか
レビューでは、interface抽象やモック設計が「未来を見越した柔軟性」に根ざしているか、
それとも単に「テストのための構文的都合」で導入されているだけかを見極める必要がある。
設計者に以下を問うことが有効:
- このinterfaceは将来の切り替えや条件変更を想定しているか?
- 具象実装が1種類だけで今後も変わらない見込みなら、あえて抽象にする意味は?
- テスト容易性が設計の主目的になっていないか?それは本当に全体設計にとって価値があるか?
これらを通じて、レビューアーとして設計の「現実性」と「将来性」のバランスを冷静に評価していくことが求められる。