【Go】現在時刻に関する処理をインターフェース化してテストしやすくするサンプルコード(DI, 依存性注入)

更新日:2025/5/9/(金) 16:43

タグ:Go

概要

サンプルコード

package main import ( "fmt" "time" ) // 現在時刻を提供するインターフェース type TimeProvider interface { Now() time.Time } type RealTimeProvider struct{} func (tp *RealTimeProvider) Now() time.Time { return time.Now() } // ExpiryCheckerは期限切れをチェックする構造体 type ExpiryChecker struct { TimeProvider TimeProvider } func NewExpiryChecker(tp TimeProvider) *ExpiryChecker { return &ExpiryChecker{TimeProvider: tp} } func (ec *ExpiryChecker) IsExpired(deadline time.Time) bool { // time.Now()を直接呼び出すとテストの実行時刻に依存してしまう // そのため、TimeProviderインターフェースを通じて現在時刻を取得する // これにより、テスト時にモックを使って現在時刻を制御できる return ec.TimeProvider.Now().After(deadline) } func main() { checker := NewExpiryChecker(&RealTimeProvider{}) // DBなどに保存されてる「期限」など deadline := time.Date(2025, 5, 9, 12, 0, 0, 0, time.Local) if checker.IsExpired(deadline) { fmt.Println("期限切れです") } else { fmt.Println("まだ有効です") } }

テストコード

package main import ( "testing" "time" ) // 現在時刻を提供するインターフェースのモック type MockTimeProvider struct { FixedTime time.Time } func (m *MockTimeProvider) Now() time.Time { return m.FixedTime } func TestIsExpired(t *testing.T) { deadline := time.Date(2025, 5, 9, 12, 0, 0, 0, time.Local) tests := []struct { name string now time.Time expected bool }{ {"期限前", time.Date(2025, 5, 9, 11, 0, 0, 0, time.Local), false}, {"期限後", time.Date(2025, 5, 9, 13, 0, 0, 0, time.Local), true}, } for _, tt := range tests { // モックを使って現在時刻を制御 // これにより、テストの実行時刻に依存しない mock := &MockTimeProvider{FixedTime: tt.now} checker := NewExpiryChecker(mock) got := checker.IsExpired(deadline) if got != tt.expected { t.Errorf("[%s] expected %v, got %v", tt.name, tt.expected, got) } } }