GolangでwebサービスのAPIを叩くCLIツールを作ろう

# はじめに
この記事は[Go (その3) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/go3)の18日目の記事です。

最近Goの勉強がてら、とある社内向けWebサービスのAPIを叩くCLIツールを書いてます。
有名どころのWebサービスならたいてい既に誰かがSDKなりCLIツールを作ってたりするので、それを使ればよいと思いますが、クローズドな社内向けのWebサービスとかだと、まぁ欲しい人が自分で書くしかないですね。

で、簡単なプロトタイプが動くようになったので、このあたりで覚えたことを整理してまとめてアウトプットしておこうかなと思います。

内容的には

* サブコマンド形式のCLIツールを作り方
    * [spf13/cobra](https://github.com/spf13/cobra)の使い方
    * viperと連携した透過的な設定ファイルの読み込み
    * version番号の埋め込み、Makefile
    * pkg/errorsによるエラーの伝搬、contextを使ったタイムアウト
* 標準のnet/httpパッケージでAPIにHTTPリクエストを投げる
    * APIのリクエスト/レスポンス型を定義してjson.Decoderでデコードする
    * Unixtime型を定義してJSONエンコード/デコード関数をカスタマイズする
    * APIを叩くClientを作る
    * 部品をつなげてAPIを呼び出す
* 標準のnet/http/httptestパッケージでHTTPサーバをモックしてテストする
    * httptestでモックサーバを作る
    * Table Driven TestでAPI呼び出しをテストする

というかんじで、特に目新しい話は出てこないですが、webサービスのAPIを叩くCLIツールを作る上で必要そうなことが一通りまとまってると、これから同じようなものを作ろうかなと思ってる人の参考になるかなぁと思ってがんばって書きます。長文です。
あとまだGo初心者なので間違った変なこと書いてたら教えてください。

# 作るもの
この記事では、サンプルとして以下の機能を持つ `hoge` コマンドを作っていきます。

`hoge` コマンドは `hoge stack show` サブコマンドに 引数 `<AppStackID>` を指定すると、 

```
$ hoge stack show 1
```

`https://hoge.example.com/api/app_stacks/1` にGETリクエストを送信し、
レスポンスとして以下のようなJSONを受け取り、

```json
{
  "app_stack":{
    "id":1,
    "name":"foo",
    "inserted_at":1481537486,
    "updated_at":1481537486
  }
}
```
  
これをパースして以下のように整形して出力します。

```
$ hoge stack show 1
id: 1, name: foo, inserted_at: 2016-12-12T19:11:26+09:00, updated_at: 2016-12-12T19:11:26+09:00
```

すごく単純ですが、これを実装するだけでもAPIを叩くCLIツールを作る上で必要なエッセンスがいろいろ詰まってます。

コードのサンプルは以下に置いておきました。
https://github.com/minamijoyo/api-cli-go-example
が、これだけだとAPIサーバがないので、実質モックしてあるtestぐらいしか使えません。
まぁ以下の説明だとコードの断片で分かりづらくて、コード全体を眺めたい時にでも見て下さい。

# サブコマンド形式のCLIツールを作り方
CLIツールを作る上でサブコマンドのグループ化や、オプションのパースなどは自前で書いてもよいですけど、まぁありもののライブラリを使うと簡単です。いろいろ選択肢はありますが、私は個人的な好みで[spf13/cobra](https://github.com/spf13/cobra)を使っています。

cobraのよいところは、サブサブコマンドとかが作りやすかったりとか、[spf13/viper](https://github.com/spf13/viper)と組み合わせて、設定ファイルの読み込みとコマンドラインのオプション指定での上書きを透過的に扱えることです。

## main関数を作る
cobraを使う場合は、main関数は基本的に `cmd.RootCmd.Execute()` に委譲するだけです。

```main.go
package main

import (
	"fmt"
	"os"

	"github.com/minamijoyo/api-cli-go-example/cmd"
)

func main() {
	if err := cmd.RootCmd.Execute(); err != nil {
		fmt.Printf("%+v\n", err)
		os.Exit(1)
	}
}
```

RootCmdの定義はviperと組み合わせる場合は、大体こんな感じです。

```cmd/root.go
package cmd

import (
	"fmt"
	"net/http"
	"runtime"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var cfgFile string

var RootCmd = &cobra.Command{
	Use:           "hoge",
	Short:         "A hoge CLI written in Go.",
	SilenceErrors: true,
	SilenceUsage:  true,
}

func init() {
	cobra.OnInitialize(initConfig)

	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default $HOME/.hoge.yml)")
	RootCmd.PersistentFlags().StringP("url", "", "https://hoge.example.com/api", "hoge endpoint URL")

	viper.BindPFlag("url", RootCmd.PersistentFlags().Lookup("url"))
}

func initConfig() {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	}

	viper.SetConfigName(".hoge")
	viper.AddConfigPath("$HOME")
	viper.AutomaticEnv()

	viper.ReadInConfig()
}

```

ポイントとして、以下の設定で、 エンドポイントURLのデフォルト値を設定しつつ、 `--url` というオプション上書きできるようにしています。

```
RootCmd.PersistentFlags().StringP("url", "", "https://hoge.example.com/api", "hoge endpoint URL")
```

また、以下の設定により、viperの `url` というキーと関連付けており、

```
viper.BindPFlag("url", RootCmd.PersistentFlags().Lookup("url"))
```

たとえば開発時は `$HOME/.hoge.yml` に以下のように書いておくと、

```$HOME/.hoge.yml
url: http://192.168.99.100:4000/api
```

開発時はローカルのAPIサーバに向けておきたいというときに、毎回オプション指定しなくても、 `コマンドのデフォルト値 < 設定ファイル < コマンドラインオプション` の優先順位で上書きができてコード内で参照する場合は、このように `viper.GetString("url")` で入力ソースを気にせずに透過的に扱うことができます。よく使うオプションが人によって違うのでバイナリに埋め込みたくないときとかに、わりと便利です。

```cmd/root.go
func newDefaultClient() (*Client, error) {
	endpointURL := viper.GetString("url")
	httpClient := &http.Client{}
	userAgent := fmt.Sprintf("hoge/%s (%s)", Version, runtime.Version())
	return newClient(endpointURL, httpClient, userAgent)
}
```

ここででてきた `Client` 型についてはあとで説明します。

またuserAgentに `Version` 変数を埋めてますが、 これはこのツールのバージョン番号でビルド時に変数で埋めます。

# versionコマンドを作る
ついでに `hoge version` サブコマンドも作っておきましょう。
cobraでは `AddCommand` で親のコマンドを指定してサブコマンドを登録できます。

```cmd/version.go
package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

var (
	Version string
	Revision string
)

func init() {
	RootCmd.AddCommand(newVersionCmd())
}

func newVersionCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "version",
		Short: "Print version",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("hoge version: %s, revision: %s\n", Version, Revision)
		},
	}

	return cmd
}
```

`Version` にはv0.0.1のようなツールのバージョン番号 `Revision` にはgitコミットのハッシュ値をビルド時に `-ldflags` オプションを指定して埋めます。ちなみに今回のようにmainパッケージ以外の変数を指定する場合は、以下のようにパッケージパスを明示的に指定しないと埋め込んでくれないようなので注意。

```
$ go build -ldflags "-X github.com/minamijoyo/api-cli-go-example/cmd.Version=v0.0.1 -X github.com/minamijoyo/api-cli-go-example/cmd.Revision=2463e27" -o bin/hoge
```

```
$ bin/hoge version
hoge version: v0.0.1, revision: 2463e27
```

## Makefile
毎度引数指定が面倒くさいので、ビルドスクリプトを書きたくなりますが、GoはC言語の文化圏のようで、この手のプロジェクトのタスク管理ツールとしてはmakeがよく使われています。というわけで、ここらでMakefileも作っておきます。
ついでに[gox](github.com/mitchellh/gox)でクロスビルドして、[ghr](github.com/tcnksm/ghr)でリリースするタスクやらlint/vet/testなど入りそうなタスクも一式準備しておく。このへんはまだまだ試行錯誤中である。
まだ悩み中の依存ライブラリの解決はいろいろ流儀があるようで、vendor配下に全部入れちゃうとか、godeps/glide/gomでバージョン管理するなど、まだ決め兼ねているのだけど、しばらく困るまでまだグローバルにまとめて入れてます。そのうちなんか考える。

```Makefile
NAME			:= hoge
VERSION			:= v0.0.1
REVISION		:= $(shell git rev-parse --short HEAD)
LDFLAGS			:= "-X github.com/minamijoyo/api-cli-go-example/cmd.Version=${VERSION} -X github.com/minamijoyo/api-cli-go-example/cmd.Revision=${REVISION}"
OSARCH			:= "darwin/amd64 linux/amd64"
GITHUB_USER		:= minamijoyo

ifndef GOBIN
GOBIN := $(shell echo "$${GOPATH%%:*}/bin")
endif

LINT := $(GOBIN)/golint
GOX := $(GOBIN)/gox
ARCHIVER := $(GOBIN)/archiver
GHR := $(GOBIN)/ghr

$(LINT): ; @go get github.com/golang/lint/golint
$(GOX): ; @go get github.com/mitchellh/gox
$(ARCHIVER): ; @go get github.com/mholt/archiver/cmd/archiver
$(GHR): ; @go get github.com/tcnksm/ghr

.DEFAULT_GOAL := build

.PHONY: deps
deps:
	go get -d -v .

.PHONY: build
build: deps
	go build -ldflags $(LDFLAGS) -o bin/$(NAME)

.PHONY: install
install: deps
	go install -ldflags $(LDFLAGS)

.PHONY: cross-build
cross-build: deps $(GOX)
	rm -rf ./out && \
	gox -ldflags $(LDFLAGS) -osarch $(OSARCH) -output "./out/${NAME}_${VERSION}_{{.OS}}_{{.Arch}}/{{.Dir}}"

.PHONY: package
package: cross-build $(ARCHIVER)
	rm -rf ./pkg && mkdir ./pkg && \
	pushd out && \
	find * -type d -exec archiver make ../pkg/{}.tar.gz {}/$(NAME) \; && \
	popd

.PHONY: release
release: $(GHR)
	ghr -u $(GITHUB_USER) $(VERSION) pkg/

.PHONY: lint
lint: $(LINT)
	@golint ./...

.PHONY: vet
vet:
	@go vet ./...

.PHONY: test
test:
	@go test ./...

.PHONY: check
check: lint vet test build
```

上記のMakefileだとビルドやパッケージング時に以下のディレクトリにファイルができちゃうので、よしなに.gitignoreしておく。

```.gitignore
# Build
bin/*
out/*
pkg/*
```

## stackコマンドグループを作る
stackコマンドを作る。これ自体はコマンドグループであって、機能はなく、 AddCommandでRootCmdに `stack` コマンドを登録しつつ、 自分に `stack show` サブコマンドを登録する。あと `hoge stack` だけ呼ばれたら `cmd.Help()` でヘルプでも出しておく。

```cmd/stack.go
package cmd

import (
	"context"	
	"fmt"
	"strconv"
	"time"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

func init() {
	RootCmd.AddCommand(newStackCmd())
}

func newStackCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "stack",
		Short: "Manage Stack resources",
		Run: func(cmd *cobra.Command, args []string) {
			cmd.Help()
		},
	}

	cmd.AddCommand(
		newStackShowCmd(),
	)

	return cmd
}
```

## stack showサブコマンドを作る
本題のstack showサブコマンドを作る。 `RunE` で `runStackShowCmd` コマンドを登録して、これが処理の実体。
ちなみに Run関数を登録するときに `Run` じゃなくて `RunE` にしておかないとerrorが返せないので注意。 

```cmd/stack.go
func newStackShowCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "show <StackID>",
		Short: "Show Stack",
		RunE:  runStackShowCmd,
	}

	return cmd
}
```

ようやく処理の実体っぽいところになってきた。

```cmd/stack.go
func runStackShowCmd(cmd *cobra.Command, args []string) error {
	client, err := newDefaultClient()
	if err != nil {
		return errors.Wrap(err, "newClient failed:")
	}

	if len(args) == 0 {
		return errors.New("StackID is required")
	}

	appStackID, err := strconv.Atoi(args[0])
	if err != nil {
		return errors.Wrapf(err, "failed to parse StackID: %s", args[0])
	}

	req := AppStackShowRequest{
		ID: appStackID,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	res, err := client.StackShow(ctx, req)
	if err != nil {
		return errors.Wrapf(err, "StackShow was failed: req = %+v, res = %+v", req, res)
	}

	appStack := res.AppStack
	fmt.Printf(
		"id: %d, name: %s, inserted_at: %v, updated_at: %v\n",
		appStack.ID, appStack.Name, appStack.InsertedAt, appStack.UpdatedAt,
	)

	return nil
}
```

やってることを順に説明していく。

```cmd/stack.go
	client, err := newDefaultClient()
	if err != nil {
		return errors.Wrap(err, "newClient failed:")
	}
```

まず、 `newDefaultClient()` で clientを作る。clientについては後述。

```cmd/stack.go
	if len(args) == 0 {
		return errors.New("StackID is required")
	}

	appStackID, err := strconv.Atoi(args[0])
	if err != nil {
		return errors.Wrapf(err, "failed to parse StackID: %s", args[0])
	}

	req := AppStackShowRequest{
		ID: appStackID,
	}
```

引数チェックをしてから、引数をパースして、 `AppStackShowRequest` を生成。この型についても後述。

ちなみに、errors.Wrapfとかやってるのは、受け取ったエラーに注釈を付けつつ、呼び出し元にエラーを伝搬させるための技法で、単純に低レイヤで発生したエラーを返すだけだと、ライブラリ呼び出し内で起きたエラーなどは何が起きたのか分からないことが多いので、各レイヤで、何の処理が失敗したのか付加情報を付けて、上位のレイヤに返すことでエラーメッセージの意味が分かりやすくなる。このへんは[Golangのエラー処理とpkg/errors](http://deeeet.com/writing/2016/04/25/go-pkg-errors/)あたりも合わせて読むとよいかと。

次に、これがAPI呼び出し処理部分にあたる。

```cmd/stack.go
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	res, err := client.StackShow(ctx, req)
	if err != nil {
		return errors.Wrapf(err, "StackShow was failed: req = %+v, res = %+v", req, res)
	}
```

10秒でタイムアウトするコンテキストを作って、 `client.StackShow` を呼び出す。ネットワーク呼び出しをするコマンドはAPIサーバと通信できない可能性があるので、このようなタイムアウト処理を仕込んでおくことは有用である。
コンテキストの説明については[Go1.7のcontextパッケージ](http://deeeet.com/writing/2016/07/22/context/)が分かりやすい。こーゆータイムアウト処理みたいなのがスマートに書けるのはGoのよいところですね。

```cmd/stack.go
	appStack := res.AppStack
	fmt.Printf(
		"id: %d, name: %s, inserted_at: %v, updated_at: %v\n",
		appStack.ID, appStack.Name, appStack.InsertedAt, appStack.UpdatedAt,
	)

	return nil
```

最後に `client.StackShow` の戻り値で、型はここでは明示されていないが `*AppStackShowResponse` から値を取り出して、表示して終了する。ここでは直接 `fmt.Printf` を読んじゃってるけど、標準入力も差し替えられるようにしておいた方がよいかもしれない。

# 標準のnet/httpパッケージでAPIにHTTPリクエストを投げる
CLIとしての呼び出しの仕組み自体は以上で、ここからAPI呼び出しの実装を作っていく。
HTTPリクエストを投げるところを書く前に、まずはリクエストとレスポンスの型を定義しておこう。

もうそろそろ忘れてそうなので、参考までにパースしたかったレスポンスのJSONを再掲しておく

```json
{
  "app_stack":{
    "id":1,
    "name":"foo",
    "inserted_at":1481537486,
    "updated_at":1481537486
  }
}
```

## APIのリクエスト/レスポンス型を定義してjson.Decoderでデコードする
APIのリクエストとレスポンスの型を定義しておくと、素のJSONを扱うよりも間違いが少なくてよいし、ドキュメンテーションにも役に立つ。
ポイントはstructを宣言する時にjsonのフィールドタグを付けておくことで、json.Decoderすると自動でstructのフィールドにマッピングできるようにしている。

```cmd/schema.go
type AppStack struct {
	ID         int      `json:"id"`
	Name       string   `json:"name"`
	InsertedAt Unixtime `json:"inserted_at"`
	UpdatedAt  Unixtime `json:"updated_at"`
}

type AppStackShowRequest struct {
	ID int `json:"id"`
}

type AppStackShowResponse struct {
	AppStack AppStack `json:"app_stack"`
}
```

あと、structの各フィールドは大文字で初めてexportしておかないと `json.Decoder` でデコードしてくれない罠があって、ハマってすごい悩んだ。
この辺も勢いでcmdパッケージ配下にまとめて書いちゃったけど、別のパッケージに分けた方がよかったような気もする。このAPIをCLIではなくて、さらに別のプログラムからSDKとして呼び出したいみたいな用途は今のところないので特に困ってはいないんだけど。

## Unixtime型を定義してエンコード/デコード関数をカスタマイズする
ところでこのAPIでは、タイムスタンプが `1481537486` みたいなUnixtimeで返ってくるので、これをそのまま表示してもヒューマンリーダブルではないし、できれば `time.Time` に変換して表示フォーマットなどを扱いやすいようにしておきたい。標準でUnixtimeを表す型はなさそうだったので、 独自に`time.Time` を包んだだけの `Unixtime` という型を定義した。このように自分で定義した型をJSONエンコド/デコードできるようにするには `MarshalJSON` と `UnmarshalJSON` を独自に定義しておくとカスタマイズした処理を挟み込める。

```cmd/schema.go
package cmd

import (
	"fmt"
	"strconv"
	"time"
)

type Unixtime struct {
	time.Time
}

func IntToUnixtime(timestamp int) Unixtime {
	return Unixtime{time.Unix(int64(timestamp), 0)}
}

func (t *Unixtime) MarshalJSON() ([]byte, error) {
	timestamp := fmt.Sprint(t.Unix())
	return []byte(timestamp), nil
}

func (t *Unixtime) UnmarshalJSON(b []byte) error {
	timestamp, err := strconv.Atoi(string(b))
	if err != nil {
		return err
	}
	t.Time = time.Unix(int64(timestamp), 0)

	return nil
}

func (t Unixtime) String() string {
	return t.Format(time.RFC3339)
}
```

ちなみにこのUnixtimeのデコード方法は、
以下のGoogleグループのスレッド内で紹介されていた
[unmarshal unix time from a json stream is of different format](https://groups.google.com/forum/#!msg/golang-nuts/Mjp1ySVdgik/_43Ewk2ABgAJ)
以下のサンプルコードを参考した。
https://play.golang.org/p/WltVTKxylT

## APIを叩くClientを作る
次に、Clientの型を定義して、コンストラクタやら、HTTPリクエストを生成したり、レスポンスのJSONをデコードしたりする共通のヘルパ関数を作っていく。

このあたりは、以下の記事をいろいろ参考にしつつ書きました。
[GolangでAPI Clientを実装する](http://deeeet.com/writing/2016/11/01/go-api-client/)
というかほぼ上記のサンプルコードからの劣化コピーなんだけど、まじdeeeetさんのブログお世話になりすぎて、ありがたい。

最初にClientの型を定義しておく。

```cmd/client.go
package cmd

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"path"

	"github.com/pkg/errors"
)

type Client struct {
	EndpointURL *url.URL
	HTTPClient  *http.Client
	UserAgent   string
}
```

次にコンストラクタを作っておく。
変数を外から差し込めるようにしてるのは、あとでダミーのMockサーバを立ててテストするため。

```cmd/client.go
func newClient(endpointURL string, httpClient *http.Client, userAgent string) (*Client, error) {
	parsedURL, err := url.ParseRequestURI(endpointURL)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to parse url: %s", endpointURL)
	}

	client := &Client{
		EndpointURL: parsedURL,
		HTTPClient:  httpClient,
		UserAgent:   userAgent,
	}
	return client, nil
}
```

HTTPリクエスト生成用のヘルパはURLを組み立てて、HTTPヘッダなどを設定する。 `req.WithContext(ctx)` のところで、呼び出し元からもらったコンテキストをHTTPリクエストに引き回しているのがポイント。

```cmd/client.go
func (client *Client) newRequest(ctx context.Context, method string, subPath string, body io.Reader) (*http.Request, error) {
	endpointURL := *client.EndpointURL
	endpointURL.Path = path.Join(client.EndpointURL.Path, subPath)

	req, err := http.NewRequest(method, endpointURL.String(), body)
	if err != nil {
		return nil, err
	}

	req = req.WithContext(ctx)

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("User-Agent", client.UserAgent)

	return req, nil
}
```

レスポンスのデコード用のヘルパメソッドも定義しておく。 `decoder.Decode` のところでJSONのレスポンスをstructにマップしてくれる、

```cmd/client.go
func decodeBody(resp *http.Response, out interface{}) error {
	defer resp.Body.Close()
	decoder := json.NewDecoder(resp.Body)
	return decoder.Decode(out)
}
```

型が `interface{}` なのにいいかんじにマップしてくれるのは黒魔術っぽい予感がするけど、たぶん中でreflectを使って型定義を参照していいかんじにゴニョゴニョしてくれてるのだろう。

# 部品をつなげてAPIを呼び出す
部品は揃ったので、ついにAPIを呼び出そう。

入力に `AppStackShowRequest` 型をもらって、適切なエンドポイントのURLを組み立て、HTTPリクエストを送り、レスポンスのJSONをデコードして、 `*AppStackShowResponse` にセットして返す。 

```cmd/stack.go

func (client *Client) StackShow(ctx context.Context, apiRequest AppStackShowRequest) (*AppStackShowResponse, error) {
	subPath := fmt.Sprintf("/app_stacks/%d", apiRequest.ID)
	httpRequest, err := client.newRequest(ctx, "GET", subPath, nil)
	if err != nil {
		return nil, err
	}

	httpResponse, err := client.HTTPClient.Do(httpRequest)
	if err != nil {
		return nil, err
	}

	var apiResponse AppStackShowResponse
	if err := decodeBody(httpResponse, &apiResponse); err != nil {
		return nil, err
	}

	return &apiResponse, nil
}
```

`client.HTTPClient.Do(httpRequest)` のところが、実際にHTTPリクエストを投げてるところ。部品が揃っているとここでやることはつなぎあわせるだけであんまりない。そういえば、HTTPステータスコードのハンドリングもした方がよいかもしれない。

なんにせよ、これで、 `hoge stack show` コマンドからのAPI呼び出しがついに完成した。
こんなかんじで、あとは必要なAPIに対応するリクエスト/レスポンスの型を定義して、それを適切なエンドポイントに投げるサブコマンドを順次で生やしていけば、よいかんじである。

# 標準のnet/http/httptestパッケージでHTTPサーバをモックしてテストする
CLIツールの機能としてはできたので、めでたしめでたし以上終了でもよいのだけど、メンテナンスを考えると早い段階でテストも書いておきたい。
Goでは標準のnet/http/httptestパッケージでHTTPサーバを簡単にモックできるので、これを使う。

## httptestでモックサーバを作る
モックサーバとテスト用のClientは複数のAPIで使いまわすので、さきにヘルパ関数を作っておく。

```cmd/mock.go
package cmd

import (
	"net/http"
	"net/http/httptest"
	"net/url"
)

func newMockServer() (*http.ServeMux, *url.URL) {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)
	mockServerURL, _ := url.Parse(server.URL)
	return mux, mockServerURL
}

func newTestClient(mockServerURL *url.URL) *Client {
	endpointURL := mockServerURL.String() + "/api"
	httpClient := &http.Client{}
	userAgent := "test client"
	client, _ := newClient(endpointURL, httpClient, userAgent)
	return client
}
```

やってることは `httptest.NewServer` でモックサーバを立てて、そのURLをテスト用のクライアント渡してリクエスト先をモックサーバに向けられるように準備しておく。
`mux` を返しているのは、これはあとで、APIごとのエンドポイントのURLを登録するのに使う。

# Table Driven TestでAPI呼び出しをテストする
Goでテストを書く場合は、 `*_test.go` というファイルに `Test〜(t *testing.T)` という関数を定義する。
また、入力と期待値を列挙したテストケース表を定義してループで回してテストするTable Driven Testという手法がよく使われている。
というわけでテストケース表を定義する。ここでは、入力として `AppStackShowRequest` と HTTPレスポンスBodyのstringを入力として、期待した `*AppStackShowResponse` が返ってくるかを確認したいので、こんなかんじにしてみた。

```cmd/stack_test.go
package cmd

import (
	"context"
	"fmt"
	"net/http"
	"reflect"
	"testing"
)

func TestStackShow(t *testing.T) {
	cases := []struct {
		req  AppStackShowRequest
		res  string
		want *AppStackShowResponse
	}{
		{
			req: AppStackShowRequest{
				ID: 1,
			},
			res: `{
				"app_stack":{
					"id":1,
					"name":"pretty",
					"inserted_at":1234567890,
					"updated_at":1481537486
				}
			}`,
			want: &AppStackShowResponse{
				AppStack: AppStack{
					ID:         1,
					Name:       "pretty",
					InsertedAt: IntToUnixtime(1234567890),
					UpdatedAt:  IntToUnixtime(1481537486),
				},
			},
		},
		{
			req: AppStackShowRequest{
				ID: 2,
			},
			res: `{"app_stack":{"updated_at":1481953207,"name":"compact_unordered","inserted_at":1234567890,"id":2}}`,
			want: &AppStackShowResponse{
				AppStack: AppStack{
					ID:         2,
					Name:       "compact_unordered",
					InsertedAt: IntToUnixtime(1234567890),
					UpdatedAt:  IntToUnixtime(1481953207),
				},
			},
		},
	}
```

テストケースのバリエーションはいろいろ考えられるけど、ここではとりあえず、ちゃんと整形されているパターンと、改行なしで並び順も異なるパターンの2パターン作っておく。
ケース表が準備できたら、モックサーバとクライアントを初期化して、テストケースごとに、 `HandleFunc` でAPIのエンドポイントに合わせて、固定のレスポンスを返すように仕込み、 `client.StackShow` を呼び出して期待値と結果を比較する。

```cmd/stack_test.go
	mux, mockServerURL := newMockServer()
	client := newTestClient(mockServerURL)

	for _, tc := range cases {
		hundlePath := fmt.Sprintf("/api/app_stacks/%d", tc.req.ID)
		mux.HandleFunc(hundlePath, func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprint(w, tc.res)
		})

		got, err := client.StackShow(context.Background(), tc.req)

		if err != nil {
			t.Fatalf("StackShow was failed: req = %+v, got = %+v, err = %+v", tc.req, got, err)
		}

		if !reflect.DeepEqual(got, tc.want) {
			t.Errorf("got=%+v, want=%+v", got, tc.want)
		}
	}
}
```

structのような構造を持ったものを一括で比較するのは `reflect.DeepEqual` を使うと各フィールドを再帰的に比較してくれるので、手抜きして比較はDeepEqualに丸投げしている。
まぁこんなかんじで対応するAPIを増やしたら、それに合わせてテストも入出力を並べるだけで、同じパターンで増やしていけるかんじになった。よさげ。

# おわりに
GolangでWebサービスのAPIを叩くCLIツールを作ってみた。
作ったものは非常に単純なものなんだけど、必要なエッセンスがいろいろ詰まっていて、学ぶことは多いですね。

この記事がこれから同じようなものを作ろうかなと思ってる人の参考になれば幸いです。