Goのinterfaceを使ったmockのテストの書き方を学んだ
プログラミング言語 Go を最近少しづつ触るようになってきました。 主に Python で書いていた簡易なスクリプトを置き換える作業なのですが、 開発の過程で、Go におけるテストコードの書き方を学習したので、備忘としてまとめます。
Goではスタブがいい感じに作れない
JavaやPythonでは、ライブラリの力を借りることによって、スタブを簡単に作ることができました。 特にJavaのどうしようもないレガシーコードと対峙する際には、 jmockit を友として、関数の振る舞いをテストコードで固めた後にリファクタリングを行う、といったファンキーな事をやっていました。
しかし、Goではそのような「リフレクションを使えば何でもあり」なことができません。(もしかしたらできるかもしれませんが、今の所見つけていません)
基本的にはDependency Injectionの発想と同様のことを行います。Interfaceに対して実装を後から定義するのです。 さっそくやってみましょう。
プロダクションコード側
構造体とインタフェースの定義
まずは、構造体 Sample
を定義します。構造体には Client
interface をもたせます。
外部パッケージからは直接 client
を操作できないようにする目的で、doGet
関数でラップします。
1// 構造体を定義する
2type Sample struct {
3 // interfaceを定義する
4 client Client
5}
6
7// 振る舞いをモックしたい
8type Client interface {
9 Get() int
10}
11
12// interfaceのGet()をラップする
13func (sample *Sample) doGet() int {
14 return sample.client.Get()
15}
interfaceの実装
次にinterfaceの実装を行います。 Go のinterfaceは他の言語と異なり、実装する側(ここでいう構造体)による 「このinterfaceを実装しますよ」 という宣言が不要です 。 レシーバを使い、interfaceに規定された関数を持った構造体を定義してあげれば良いのです。
今回は hogeClientImpl
という名前で実装します。
1// Client interface の実装をする構造体
2type hogeClientImpl struct {
3}
4
5// レシーバで実装する
6func (client *hogeClientImpl) Get() int {
7 return 1
8}
これによって、プロダクションコードの実装においては、例えば以下のように、hogeClientImpl
を外から渡すことができます。
仮に hogeClientImpl
の実装がinterfaceの定義を満たしていなければ、コンパイルエラーになります。
1func hogeMain() int {
2 sample := &Sample{&hogeClientImpl{}}
3 // 1が返却される
4 return sample.doGet()
5}
テストコード側
interfaceの実装
次にテストコードを作ってみましょう。テストコードファイル側でも同様に interfaceの定義を満たす構造体を定義してあげればOKです。
ここでは testClientImpl
とします。
1type testClientImpl struct {
2}
3
4func (c *testClientImpl) Get() int {
5 return 2
6}
interfaceと同じ関数を規定したので、Client
として引数に渡すことが可能となりました。
1func TestMainRequest(t *testing.T) {
2 // testClientImpl の実体をセットする
3 sample := &Sample{&testClientImpl{}}
4 // testClientImpl#Getが呼ばれ、2が返却される
5 res := sample.doGet()
6 if res != 2 {
7 t.Error("response should be 2")
8 }
9}
まとめ
今回はinterfaceを使って Go のテストコードを書いてみました。
- interfaceを持った構造体を定義する(構造体A)
- interfaceを実装した構造体を定義する(構造体B)
- 構造体Aの初期化時に構造体Bを渡してあげる
- テスト時には、テスト用に作成した構造体(構造体C)を構造体Aの初期化時にわたしてあげる
といった感じです。
interface定義からmockを自動生成してくれるライブラリ もありましたが、当面は自前で構造体定義して頑張ろうと思っています。