出力を入力へ

プログラミングに関する自分が考えた事を中心にまとめます

go-tfeで単体テスト

go-tfe概要

go-tfeはTerraform Cloud (およびTerraform Enterprise)のためのGo SDK。 go-tfeを利用することで、Terraform Cloudの各種APIを利用したツールを実装することができる。

github.com

go-tfeのテスト

go-tfeを利用したツールを実装するときに単体テストをどうするかが悩ましいところ。 go-tfeには単体テストに相当するものがなく(?)、すべてE2Eテストになっている。 また、Terraform Cloud APIをやりとりするclientはinterfaceが定義されていないので、mockを生成してテストに利用することができない。

github.com

// Client is the Terraform Enterprise API client. It provides the basic
// connectivity and configuration for accessing the TFE API
type Client struct {
    baseURL           *url.URL
...
}

リクエスト先のbaseURLを書き換えることはできるので、Terraform Cloud自体のmockを生成すればテストの実装が可能になるかもしれないがこれは厳しい。 terraformもTerraform Cloudも機能追加が活発なので、そのAPIを利用するならテストの担保はしておきたい。 できれば単体テストとしてテストを実装したいので、いきなりE2Eテストは避けたい。

mockの利用

go-tfe自身は利用していないが、go-tfeの各種サブ機能に関するmockコードは生成されている。 GoMockを用いてmockが生成されているので、go-tfeを利用したアプリを実装する場合はこのmockを利用して単体テストを書くとよさそう。

ただし、前述の通りclient自体にはinterfaceは定義されておらず、mockコードにもclientに関するコードは存在しない。 このため、go-tfeを用いたツールを実装するときにはclientを引数などには含めず、サブ機能ごとのインスタンス(こちらはinterfaceが定義されている)に依存した実装にするとよさそう。

例えば、workspace名とVariables APIを引数としてVariableListを返すmyFuncの単体テストは以下のようになる。

package test

// func myFunc(ctx context.Context, workspace string, variables tfe.Variables)

import (
    "context"
    "testing"

    "github.com/golang/mock/gomock"
    tfe "github.com/hashicorp/go-tfe"
    "github.com/hashicorp/go-tfe/mocks"
)

func TestListVariables(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockVariables := mocks.NewMockVariables(ctrl)
    ctx := context.TODO()

    var items []*tfe.Variable
    mockVariables.EXPECT().
        List(ctx, "w-test-no-vars-workspace", nil).
        Return(&tfe.VariableList{
            Items: items,
        }, nil).
        AnyTimes()

    variableList, err := myFunc(ctx, "w-test-no-vars-workspace", mockVariables)

    if err != nil {
        t.Errorf("expect no error, got error: %v", err)
    }
    if len(variableList.Items) != 0 {
        t.Errorf("expect no variables, got variables: %v", variableList.Items)
    }
}