Skip to content

さいきん気に入っているテストの書き方

以前からずっとある方法だけど、関数の内部で xxxInternal という形で実装を分けて、単体テストでは xxxInternal を使ってテストする書き方を気に入っている。

例えばKinesisにPutするメソッドがあったとき、

type Writer struct{ ... }
func (w *Writer) BulkPost(ctx context.Context, metrics []*Metric) error {
// Kinesisを初期化する
k := kinesisFromConfig(w)
records := make([]types.PutRecordsRequestEntry, len(metrics))
for i, m := range metrics {
records[i] = metricToRequestEntry(m)
}
input := &kinesis.PutRecordsInput{
Records: records,
StreamName: aws.String("dummy"),
}
_, err := k.PutRecords(ctx, input)
return err
}

このままでは本当のKinesisに投げてしまって困るので、Writer.BulkPost()bulkPostInternal で分ける。bulkPostInternal はパッケージ内でのみ参照できるインターフェイスでKinesisクライアントを受け取る。

type Writer struct{ ... }
func (w *Writer) BulkPost(ctx context.Context, metrics []*Metric) error {
k := kinesisFromConfig(w)
return bulkPostInternal(ctx, metrics, k)
}
type recordsPutter interface {
// Kinesis SDKの型にあわせておく
PutRecords(ctx context.Context, input *kinesis.PutRecordsInput, opts ...func(*kinesis.Options)) (*kinesis.PutRecordsOutput, error)
}
func bulkPostInternal(ctx context.Context, metrics []*Metric, putter recordsPutter) error {
records := make([]types.PutRecordsRequestEntry, len(metrics))
for i, m := range metrics {
records[i] = metricToRequestEntry(m)
}
input := &kinesis.PutRecordsInput{
Records: records,
StreamName: aws.String("dummy"),
}
_, err := putter.PutRecords(ctx, input)
return err
}

これで自由にモックできるようになったので、テストでは関数にメソッドを生やしてモックする。

type putterFunc func(metrics []*Metric) (*kinesis.PutRecordsOutput, error)
// recordsPutterインターフェイスを実装
func (f putterFunc) PutRecords(ctx context.Context, input *kinesis.PutRecordsInput, opts ...func(*kinesis.Options)) (*kinesis.PutRecordsOutput, error) {
return f(input)
}
func TestBulkPost(t *testing.T) {
metrics := make([]*Metric, 10)
bulkPostInternal(context.Background(), metrics, putterFunc(func(metrics []*Metric) (*kinesis.PutRecordsOutput, error) {
return nil, errors.New("error")
}))
}

こうすると、

  • モックの差し替えも関数を書くだけなので簡単
  • テストしたい対象の近くにモックの実装があるので、モックが返している値がすぐ見える
  • パッケージの外からは必要なインターフェイスしか見えない

特に最後の、モックのためのインターフェイスを公開する必要がなくて嬉しい。上記の場合、

type Writer struct{ ... }
func (w *Writer) BulkPost(ctx context.Context, metrics []*Metric) error

とだけ見えていて、モック用の余計なものが出てこない。