kazasiki's blog

プログラミングとかゲームとか

自分が重宝してるGolangのtesthelper

golangに限らず、テストを書いてると同じようなパターンを何回も書くことが多いです。 そこで私が重宝してるヘルパー関数を紹介します。

golangの関数を書く場合、戻り値にerrorと何かを返すパターンが多いですね。 そこで以下のヘルパー関数を用意します。

// CheckErr エラーの存在チェック
func CheckErr(t *testing.T, i int, ok bool, err error) bool {
    t.Helper()
    if ok {
        if err != nil {
            t.Errorf("[%d] err should be nil, but got:%s", i, err)
            return true
        }
        return false
    }
    if err == nil {
        t.Errorf("[%d] err should not be nil, but got:%s", i, err)
        return true
    }
    return true
}

err以外のレスポンスをチェックする場合はfalseを返します。
iはTDDのループのindexを入れます。TDDしてるとどこででたエラーが出たかわからなくなることがあるので...
okはerrが出るテストケースかどうかをboolで入れます(trueならエラーがない)
errはテスト対象の関数のerror戻り値を入れます

例えばこんな感じで使います。
errorを返す場合は、他の戻り値はどうせゼロ値なのでチェックしないでしょう。

func Something(x int) (string, error){
    if x < 0 {
        return "", errors.New("")
    } else {
        return strconv.Itoa(x), nil
    }
}

func TestSomething(t *testing.T){
    testTable := []struct{
        x int
        res string
        isErr bool
    }{
        {x: 1, res: "1", isErr: false},
        {x: -1, isErr: true},
    }
    for i, tt := range testTable {
        res, err := Something(i)
        if !CheckErr(t, i, tt.isErr, err) {
            if w, g := tt.x, res; w != g {
                t.Errof("unmatch w: %s, g: %s", w, g)
            }
        }
    }
}

さらに例えばerrorがcodeを返すように拡張(?)してる場合もあるでしょう。

// ErrCodeを返すMyErrorがあるとする
type MyError interface{
    Error() string
    ErrCode() uint32
}

type HogeError struct{}

func (e HogeError) Error() string {
    return "error"
}

func (e HogeError) ErrCode() uint32 {
    return 1001
}

その場合はCheckErrも以下のように拡張します。

// CheckErrCode 想定されるエラーコードがあればそれを確認する
func CheckErrCode(t *testing.T, i int, code uint32, err error) bool {
    t.Helper()
    noErr := code == 0
    if !noErr && err != nil {
        if terr, ok := errors.Cause(err).(MyError); ok {
            if w, g := terr.ErrCode(), code; w != g {
                t.Errorf("[%d] err_code unmached. want:%d, but got %d", i, w, g)
            }
        } else {
            t.Errorf("[%d] err type unmached. %T", i, terr)
        }
    }
    return CheckErr(t, i, noErr, err)
}

func渡したり構造体作ったりもせずシンプルな関数なので適当に拡張できて便利です。