web-dev-qa-db-ja.com

テスト中にtime.Now()をグローバルにスタブする簡単な方法はありますか?

コードの一部は時間に敏感であるため、何かを予約してから30〜60秒でリリースできるようにする必要があります。これはtime.Sleep(60 * time.Second)

私は時間インターフェイスを実装しましたが、テスト中は このgolang-nutsディスカッション と同様に、時間インターフェイスのスタブ実装を使用します。

ただし、time.Now()は複数のサイトで呼び出されます。これは、実際にスリープした時間を追跡するために変数を渡す必要があることを意味します。

time.Now()をグローバルにスタブする別の方法があるかどうか疑問に思っていました。システムクロックを変更するためにシステムコールを行うことはできますか?

時間パッケージを基本的にラップする独自の時間パッケージを作成して、変更することはできますか?

私たちの現在の実装はうまく機能しています。私は初心者です。他のアイデアがあるかどうか知りたいですか?

33
samol

カスタムインターフェイスを実装すると、は既に正しい方法になっています。投稿したgolangナットスレッドから次のアドバイスを使用すると思います。


type Clock interface {
  Now() time.Time
  After(d time.Duration) <-chan time.Time
}

具体的な実装を提供する

type realClock struct{}
func (realClock) Now() time.Time { return time.Now() }
func (realClock) After(d time.Duration) <-chan time.Time { return time.After(d) }

およびテスト実装。


オリジナル

テスト中(または一般的に)にシステム時間を変更するのは悪い考えです。テストの実行中にシステム時間に依存するものがわからないため、デバッグに何日も費やすことで困難な方法を見つけたくありません。しないでください。

時間パッケージをグローバルにシャドウイングする方法もありません、それを行うとインターフェースソリューションではできなかったことができなくなります。標準ライブラリを使用し、モックタイムライブラリに切り替える機能を提供する独自のタイムパッケージを作成して、それが煩わしいインターフェイスソリューションで渡す必要があるタイムオブジェクトであるかどうかをテストできます。

コードを設計およびテストする最良の方法は、可能な限りステートレスにすることです。機能をテスト可能なステートレスパーツに分割します。これらのコンポーネントを個別にテストする方がはるかに簡単です。また、副作用が少ないということは、コードを同時に実行するのがはるかに簡単になることを意味します。

42
nemo

bouk/monkey package を使用して、コード内のtime.Now()呼び出しを偽に置き換えます。

package main

import (
    "fmt"
    "time"

    "github.com/bouk/monkey"
)

func main() {
    wayback := time.Date(1974, time.May, 19, 1, 2, 3, 4, time.UTC)
    patch := monkey.Patch(time.Now, func() time.Time { return wayback })
    defer patch.Unpatch()
    fmt.Printf("It is now %s\n", time.Now())
}

これは、システムの依存関係を偽装するテストでうまく機能し、 abused DI pattern を回避します。本番コードはテストコードとは別のままであり、システムの依存関係を効果的に制御できます。

20
Allen Luce

Now()など、モックする必要のあるメソッドが少ない場合、テストで上書きできるパッケージ変数を作成できます。

package foo

import "time"

var Now = time.Now

// The rest of your code...which calls Now() instead of time.Now()

次に、テストファイルで:

package foo

import (
    "testing"
    "time"
)

var Now = func() time.Time { return ... }

// Your tests
12
Flimzy

また、スタブtime.Now依存関係を関数として注入できます。

func moonPhase(now func() time.Time) {
  if now == nil {
    now = time.Now
  }

  // use now()...
}

// Then dependent code uses just
moonPhase(nil)

// And tests inject own version
stubNow := func() time.Time { return time.Unix(1515151515, 0) }
moonPhase(stubNow)

動的言語のバックグラウンド(Rubyなど)から来た場合は、すべてが少しallいことになります:(

6
dolzenko

Googleの結果から、比較的簡単な解決策を見つけました: Here

基本的な考え方は、time.Now()を取得するために別の関数呼び出し「nowFunc」を使用することです。メインで、この関数を初期化してtime.Now()を返します。テストでは、この関数を初期化して、固定された偽の時間を返します。

2
Fei