Goのinterfaceを使ったmockのテストの書き方を学んだ

Goのinterfaceを使ったmockのテストの書き方を学んだ
Page content

プログラミング言語 Go を最近少しづつ触るようになってきました。 主に Python で書いていた簡易なスクリプトを置き換える作業なのですが、 開発の過程で、Go におけるテストコードの書き方を学習したので、備忘としてまとめます。

Goではスタブがいい感じに作れない

JavaやPythonでは、ライブラリの力を借りることによって、スタブを簡単に作ることができました。 特にJavaのどうしようもないレガシーコードと対峙する際には、 jmockit を友として、関数の振る舞いをテストコードで固めた後にリファクタリングを行う、といったファンキーな事をやっていました。

しかし、Goではそのような「リフレクションを使えば何でもあり」なことができません。(もしかしたらできるかもしれませんが、今の所見つけていません)

基本的にはDependency Injectionの発想と同様のことを行います。Interfaceに対して実装を後から定義するのです。 さっそくやってみましょう。

プロダクションコード側

構造体とインタフェースの定義

まずは、構造体 Sample を定義します。構造体には Client interface をもたせます。 外部パッケージからは直接 client を操作できないようにする目的で、doGet 関数でラップします。

// 構造体を定義する
type Sample struct {
  // interfaceを定義する
	client Client
}

// 振る舞いをモックしたい
type Client interface {
	Get() int
}

// interfaceのGet()をラップする
func (sample *Sample) doGet() int {
	return sample.client.Get()
}

interfaceの実装

次にinterfaceの実装を行います。 Go のinterfaceは他の言語と異なり、実装する側(ここでいう構造体)による 「このinterfaceを実装しますよ」 という宣言が不要です 。 レシーバを使い、interfaceに規定された関数を持った構造体を定義してあげれば良いのです。 今回は hogeClientImpl という名前で実装します。

// Client interface の実装をする構造体
type hogeClientImpl struct {
}

// レシーバで実装する
func (client *hogeClientImpl) Get() int {
	return 1
}

これによって、プロダクションコードの実装においては、例えば以下のように、hogeClientImpl を外から渡すことができます。 仮に hogeClientImpl の実装がinterfaceの定義を満たしていなければ、コンパイルエラーになります。

func hogeMain() int {
  sample := &Sample{&hogeClientImpl{}}
  // 1が返却される
	return sample.doGet()
}

テストコード側

interfaceの実装

次にテストコードを作ってみましょう。テストコードファイル側でも同様に interfaceの定義を満たす構造体を定義してあげればOKです。 ここでは testClientImpl とします。

type testClientImpl struct {
}

func (c *testClientImpl) Get() int {
	return 2
}

interfaceと同じ関数を規定したので、Client として引数に渡すことが可能となりました。

func TestMainRequest(t *testing.T) {
  // testClientImpl の実体をセットする
  sample := &Sample{&testClientImpl{}}
  // testClientImpl#Getが呼ばれ、2が返却される
	res := sample.doGet()
	if res != 2 {
		t.Error("response should be 2")
	}
}

まとめ

今回はinterfaceを使って Go のテストコードを書いてみました。

  • interfaceを持った構造体を定義する(構造体A)
  • interfaceを実装した構造体を定義する(構造体B)
  • 構造体Aの初期化時に構造体Bを渡してあげる
  • テスト時には、テスト用に作成した構造体(構造体C)を構造体Aの初期化時にわたしてあげる

といった感じです。

interface定義からmockを自動生成してくれるライブラリ もありましたが、当面は自前で構造体定義して頑張ろうと思っています。

soudegesu avatar
About soudegesu
Software Engineer
comments powered by Disqus