本日も乙

ただの自己満足な備忘録。

AWS SDK for Goを使ったテストを書く

久しぶりのAWSネタです。
Go言語でAWSリソースを操作する場合、 AWS SDK for Go を使うことが多いと思います。
プログラムソースを書いたら当然(?)テストも書くことが重要になってきますよね(ですよね?)。

このときにAWS SDK for Goを使ってのテストってどのように書くのかいろいろ調べてみました。
その中で最もオーソドックスなのが、AWS Developers Blogで紹介されているインタフェースでモックを使う方法です。

aws.amazon.com

URLリンク先はSQSを使ったテストが紹介されていますが、今回私がEC2を操作する機会がありましたので、EC2を使ったテストを書いてみることにしました。
サンプルソースコードをGitHubに公開しています。EC2インスタンスのIDを返す簡単なサンプルです。

github.com

ec2iface.EC2API というEC2のインタフェースがあるのでこれを使ってモックが作れるようにしてあげます。
具体的には以下のコードで示しますが、 ec2iface.EC2API をラップした EC2iface というインタフェースを作ってあげて、インタフェースを満たすメソッド(今回は ListIds() )を定義します。

// ec2client.go
// EC2iface -
type EC2iface interface {
    ListIds() ([]string, error)
}

// Instance -
type Instance struct {
    client ec2iface.EC2API
}

// NewClient is construct of ec2 object.
func NewClient(svc ec2iface.EC2API) EC2iface {
    return &Instance{
        client: svc,
    }
}

// ListIds return list of ids.
func (i *Instance) ListIds() ([]string, error) {
    var instances []string

    resp, err := i.client.DescribeInstances(&ec2.DescribeInstancesInput{})
    if err != nil {
        return instances, err
    }

    for _, res := range resp.Reservations {
        for _, instance := range res.Instances {
            instances = append(instances, *instance.InstanceId)
        }
    }
    return instances, nil
}

呼び出すときはこのようにします。

// main.go
func main() {
    sess := session.Must(session.NewSession(
        &aws.Config{
            Region: aws.String(region),
        }))

    client := NewClient(ec2.New(sess))
    instances, err := client.ListIds()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(instances)
}

テストはこんな感じです。
mockEC2iface というのがモック用クライアントです。このモックからテスト用のインスタンスIDを返す DescribeInstances() メソッドを作成します。テスト TestListEC2Ids() はこのメソッドを使うことでEC2 APIにアクセスすることなくテストができます。

// ec2client_test.go
type mockEC2iface struct {
    ec2iface.EC2API
}

func (m *mockEC2iface) DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {

    return &ec2.DescribeInstancesOutput{
        Reservations: []*ec2.Reservation{
            {
                Instances: []*ec2.Instance{
                    {
                        InstanceId: aws.String("i-12345678"),
                    },
                    {
                        InstanceId: aws.String("i-abcdefgh"),
                    },
                },
            },
        },
    }, nil
}

func TestListEC2Ids(t *testing.T) {
    mockClient := NewClient(&mockEC2iface{})
    instances, err := mockClient.ListIds()
    if err != nil {
        t.Errorf("Expected no error, but got %v.", err)
    }
    if len(instances) == 0 {
        t.Errorf("Expected list of ec2 instance id, but got empty.")
    }
    expectedInstances := []string{
        "i-12345678",
        "i-abcdefgh",
    }
    for i, instance := range instances {
        if expectedInstances[i] != instance {
            t.Errorf("Expected %s, but got %s.", expectedInstances[i], instance)
        }
    }
}

参考