さいきん気に入っているテストの書き方
以前からずっとある方法だけど、関数の内部で 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とだけ見えていて、モック用の余計なものが出てこない。