goldie でAPIテスト Go Golang goldie その1

今回はgoldieというライブラリを使ったGolden testを実装していきます。

Golden Test

Goleden Testとは .golden拡張子のファイルにAPIの結果(jsonなどを)保持しておき、テスト時に出力結果に変更がないかを比較するテスト手法です。

goldie

goldieはGo言語用の Golden Test ライブラリです。 .goldenファイルの生成と比較をしてくれます。

github.com

一般的にテストファイルと.goldenファイルは以下のような構成になります。

.
├── main.go
├── test
│   ├── testdata
│   │   └── TestUsers.golden
│   └── user_test.go

APIテスト

Golden Testと goldieについて理解したところでテストを作成していきます。

テストするのはuserを扱う単純なAPIです。

Method URL 概要
GET /users 全てのユーザーを取得
POST /users ユーザーを作成

まずは上記のうち /usersのテストを行います。

以下がテストコードです。 *FWやDBに依存しているところがありますが見逃してください。

package test_integration

import (
    ...省略

    "github.com/sebdah/goldie/v2"
)

func setUp(t *testing.T) *gin.Engine {
    // TODO change by env
    conn, err := sql.Open("mysql", "root:@tcp("+os.Getenv("DB_HOST")+":3306)/"+"DB_NAME")
    if err != nil {
        t.Fatalf(err.Error())
    }
    tx, err := conn.Begin()
    if err != nil {
        t.Fatalf(err.Error())
    }

    infraTx := infrastructure.Tx{
        Tx: tx,
    }

    userController := controllers.NewUserController(infraTx)
    _, err = userController.Interactor.UserRepository.Store(domain.User{
        ID:        0,
        FirstName: "firstName",
        LastName:  "lastName",
    })
    if err != nil {
        t.Fatalf(err.Error())
    }

    r := infrastructure.NewRouter(userController)
    t.Cleanup(func() {
        if err := infraTx.RollBack(); err != nil {
            t.Fatalf("failed RollBack")
        }
    })
    return r
}

func TestUsers(t *testing.T) {
    r := setUp(t)
    api := "/users"
    req := httptest.NewRequest(http.MethodGet, api, nil)
    rec := httptest.NewRecorder()
    r.ServeHTTP(rec, req)
    if rec.Code != http.StatusOK {
        t.Fatal(rec)
    }
    ret := regexp.MustCompile(`\"id\":[0-9]+`).ReplaceAllString(rec.Body.String(), `"id":0`)
    g := goldie.New(t)
    g.AssertJson(t, t.Name(), ret)
}

順に解説していきます。

setUp()

setUp()関数ではテスト用のDBへの接続とダミーデータの生成を行ない ルーターの構造体(*gin.Engine)を返しています。

テストをトランザクション内で実行している事に注目してください。

    // トランザクション開始
    tx, err := conn.Begin()

    // user生成
    _, err = userController.Interactor.UserRepository.Store(domain.User{
        ID:        0,
        FirstName: "firstName",
        LastName:  "lastName",
    })

    // 終了後にロールバック
    t.Cleanup(func() {
        if err := infraTx.RollBack(); err != nil {
            t.Fatalf("failed RollBack")
        }
    })

テスト毎にロールバックする事で毎回DBを同じ状態からテストする事ができます。

また、本来なら ginmysqlに依存しない様に独自の構造体やパッケージに分けるべきですが今回は割愛します。

TestUsers()

つぎに実際のテストコードを見ていきます。

以下のコードでAPIを叩いてレスポンスを取得しています。

   api := "/users"
    req := httptest.NewRequest(http.MethodGet, api, nil)
    rec := httptest.NewRecorder()
    r.ServeHTTP(rec, req)
    if rec.Code != http.StatusOK {
        t.Fatal(rec)
    }

次に regexp パッケージを使用して id0に書き換えています。 これにより取得したすべてのuserのidは "id":0 となります。 これは idカラムはオートインクリメントになっており、番号が保証されていないからです。

   ret := regexp.MustCompile(`\"id\":[0-9]+`).ReplaceAllString(rec.Body.String(), `"id":0`)

最後に goldie パッケージを使用して比較を行います。

   g := goldie.New(t)
    g.AssertJson(t, t.Name(), ret)

以上がコードの解説となります。

テストの実行

Golden テストの実行は2段階のコマンドにより行われます。

まずは -update フラグを使用して.goldenファイルを更新します。

go test -v -run ${TARGET} ./test/... -update

これにより、{テスト名}.golden ファイルにAPIのレスポンスが書き出されます。

[{\"id\":0,\"first_name\":\"firstName\",\"last_name\":\"lastName\",\"full_name\":\"firstName lastName\"}]"

setUp() 関数で生成したuserを取得できました。

以後テストしたい時は -updateフラグを外す事で既存の .goldenファイルと比較を行い、結果が.goldenファイルと異なるか.goldenファイル自体が見つからない場合はエラーを返します。

go test -v ./test/...

以上が goldieを用いたGolden testの流れとなります。 今回はシンプルなGETのAPIを叩いただけでしたが、次回はもう少し実務ベースのテストとgoldenファイルのテクニックを紹介したいと思います。

最後に、 このブログではweb開発について発信していくのでまたご覧頂けると嬉しいです。 最後までお読み頂きありがとうございました。