LoginSignup
63
45

More than 5 years have passed since last update.

goでmockを使ったテストをする

Last updated at Posted at 2017-04-03

モックを使ったテストって?

テストのために本物のオブジェクトを使わず、テスト用に差し替える手法です。

モックを用いたテスト自体、否定派・賛成派あるのでいろいろ意見はありますが
階層型のアーキテクチャを選定していると、一番下のレイヤまで気を配らないと
上位層のテストがかけないのはしんどいですしよね。

例えば

  • 他社API処理

通信コケてCIガー、実行時間ガー

  • データベースがらみ

並列実行すると、コケるワー、実行時間ガー

テストの障害が増えると、テストを書かなくなりますし、メンテナンスも大変に
なっていくので使える所は使った方が良いのかな、と。

一番ストレートなやり方 (例)

goの記事というか基本的なDIの活用例っぽいですが...

  • 基本的にinterfaceに依存する。
  • DI(Dependency Injection)を活用する。

  • テスト対象のサンプルコード


package main

import (
    "errors"
)

type APIClient interface {
    Call(string) (map[string]string, error)
}

type UserRegister struct {
    client APIClient // interfaceに依存させる。
}

type User struct {
    Name string
}

// 引数に受け取ったnameを元に別APIでuserを登録する
func (u *UserRegister) Register(name string) (*User, error) {
    apiResult, err := u.client.Call(name)
    if err != nil {
        return nil, err
    }
    resultName, exist := apiResult["name"]
    if !exist {
        return nil, errors.New("apiResult must have name")
    }

    return &User{resultName}, nil
}
  • テストコード

package main

import (
    "testing"
)

// mockする構造体を定義する
type APIClientMock struct {
}

// APIClientInterfaceを満たすようにモックを実装する
func (a *APIClientMock) Call(name string) (map[string]string, error) {
    return map[string]string{"name": name}, nil
}

func TestRegister(t *testing.T) {
    // Mockを外部注入する
    target := &UserRegister{&APIClientMock{}}
    expected := "Mike"
    user, err := target.Register(expected)
    if err != nil {
        t.Fatalf("Register error: %v", err)
    }
    actual := user.Name
    if actual != expected {
        t.Fatalf("name Must be %s", expected)
    }
}

goには継承の概念がない代わりにembedで実装の再利用を可能に
していますが、型判定は通りません。
オブジェクト指向っぽく書く場合はinterfaceを使う方が良いですね。
(goに限らず...な所もありますが)

便利ライブラリ

上記の例では省きましたがより厳密にテストをするのであれば、
UserRegisterの責務である、APIClient.Call()の呼び出しの担保されたかまで考慮しないといけないので、
もう少し実装をかけないといけなくて、それはそれで面倒ですよね。
毎回作るのも面倒臭いし。

他の言語でも大概その手のライブラリはありますし、goにも勿論あります。

こちらのライブラリなら以下のような使い心地で簡単にできます。

//trueで呼ばれたら1を返す
mock.EXPECT().Method(true).Return(1)
// 初回は1,"first"で呼ばれることを保証
firstCall := mock.EXPECT().SomeMethod(1, "first")
// 1で呼ばれた後で2,"second"で呼ばれることを保証
secondCall := mock.EXPECT().SomeMethod(2, "second").After(firstCall)
// 2で呼ばれた後に3,"third"で呼ばれることを保証
mockObj.EXPECT().SomeMethod(3, "third").After(secondCall)

mockgenでmockのgenerateも出来るのでとても便利です :)

関連

63
45
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
45