小野マトペの納豆ペペロンチーノ日記スマートフォン用の表示で見る小野マトペの納豆ペペロンチーノ日記2014-08-14Golangで関数をグローバル変数に代入してテスト時にスタブするGoこんにちは、小野マトペです。昨日、現在時刻によって条件分岐するロジックを含むGoプログラムを書いていて、どうテストするか困ったのですが、うまい(と思う)やり方を思いついたのでここに書いておきます。書いていたのはまあだいたいこういうソースコードです。fmt.Sprintf("こんにちは%sさん。fmt.Sprintf("こんばんは%sさん。main.goこんにちはマトペさん。今は12時ですよ!で、このGreet関数をテストしたいんですが、このままでは夜の挨拶を出すのに18時まで待つ必要があります。それでは困るのでtime.Nowの部分をなんとかちょろまかしていつでも全ての条件をテスト出来るようしないといけません。いわゆるスタブ化ですね。思い浮かぶのは、Greet関数を下のようにDIっぽく依存性である現在時刻を引数から受け取るように変更する方法だと思います。fmt.Sprintf("こんにちは%sさん。fmt.Sprintf("こんばんは%sさん。}}これなら任意の時間の場合のGreet関数のユニットテストが書けますが、現在時刻のテストのために本来不要な引数が増えるのは大げさで好ましくありません(好みの問題です)。それに、結局コールスタックのどこかでtime.Now()を呼ぶ必要があり、その呼び出しポイントを含むテストはスタブ化できません。ここはやはりtime.Now関数をテスト実行時だけ動的に上書きしたいところですが、Golangはあまりスタブ化に優しい言語ではなく、例えばtimeパッケージをテスト時に動的に他のパッケージに差し替えるとかはできません。これが自作パッケージで提供している構造体かなにかであれば、interfaceでもなんでも使ってスタバブルにすればいいと思いますが...。Overflow)で、どうしたかというと、タイトル通りですがmain.goでtime.Nowの関数ポインタをグローバル変数に代入しておき、テスト時にmain_test.goで任意の時刻を返す関数にさしかえるという方法を使いました。fmt.Sprintf("こんにちは%sさん。fmt.Sprintf("こんばんは%sさん。}}Goは関数が一級市民なのでシグネチャさえ合致すれば変数に代入できます。time.Time型として宣言され、time.Now関数で初期化されます。これを次のようにテストします。"こんにちはまとぺさん。"こんばんはまとぺさん。0.006sテスト内で宣言しているsetNow(time.Time)関数は、グローバル変数のtimeNowFuncに、任意の時刻を返す関数を代入する関数です。これでテストごとにtimeNowFuncの返す時刻をコントロールできるので、任意の時刻のテストが書けます。面倒なスタブ用クラス実装や不要な引数の引き回しなしで、テスト時にのみ作用するスタブです。グローバル変数と聞いてつらくなる人もいるかもしれませんが、Goのグローバル変数は実際にはパッケージスコープで、頭文字を小文字にしておけばパッケージの外からアクセスできないので、比較的クリーンです。Goはテストがコードと同じパッケージ空間で実行できるので、テストコードに対してのみExportされたパラメータとして扱えます。この、テスト時にスタブしたい関数の呼び出しをグローバル変数でプロキシしておいてtest側の初期化で差し替える方法は、同一パッケージ内であればモック化のための追加の記述量が少なく、データベースのテストなど、いろいろ応用が利きそうだなと思いました。複数パッケージにまたがるtime.Now()を一括で差し替えたいような場合は、スタブプロキシ用のパッケージを作ってそこのExported(大文字から始まる)グローバル変数にセットしてやればよさそうですね。Globalを使えばこんな感じでちょっとかっこよくグローバル変数を呼べます。紛らわしいような気もしますが。安全なWebフォームを作るこんにちは。GoでWeb開発していますか?私はしていません。GroupsのRSS/Atomフィードってどこにあるんだっけカメラ初心者がカメラ沼に入ってみた。ディスクキャッシュを保存する容量がないと言い張るときはTimeMachineローカルスナップショットを消せ月別アーカイブはてなブログをはじめよう!ono_matopeさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。