Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ioのテストをうまくやりたい

ktnyt
April 28, 2022

 ioのテストをうまくやりたい

io.Readerやio.Writer、便利だけどラップするとテストが面倒くさい...... そんな問題に向き合ってみた話です。
関連記事:https://zenn.dev/ktnyt/articles/ba4b08504eb5ba

ktnyt

April 28, 2022
Tweet

More Decks by ktnyt

Other Decks in Programming

Transcript

  1. Morph: JSONを構造体リテラルに変換 { "null": null, "bool": true, "number": 3.1415, "string":

    "foo", "array": [ null, true, 3.1415, "foo" ] } map[string]any{ "null": nil, "bool": true, "number": 3.1415, "string": "foo", "array": []any{ nil, true, 3.1415, "foo", }, }
  2. Writerに書き込んで文字列結合 func WriteJoined(w io.Writer, ss []string, sep string) (int, error)

    { if len(ss) == 0 { return 0, nil } head, tail := ss[0], ss[1:] n, err := io.WriteString(w, head) if err != nil { return n, err } if len(tail) > 0 { m, err := io.WriteString(w, sep) n += m if err != nil { return n, err } } m, err := WriteJoined(w, tail, sep) return n + m, err }
  3. 正常系のテスト func TestJoinWriter(t *testing.T) { buf := &bytes.Buffer{} in :=

    []string{"foo", "bar", "baz"} sep := "," exp := strings.Join(in, sep) n, err := WriteJoined(buf, in, sep) if n != len(exp) || err != nil { t.Errorf( "WriteJoined(...) = %d, %v: wanted %d, nil", n, err, len(exp), ) } got := buf.String() if got != exp { t.Errorf( "unexpected bytes written (-exp, +got): %v", diff.LineDiff(exp, got).Join(), ) } }
  4. 正常系以外は? func WriteJoined(w io.Writer, ss []string, sep string) (int, error)

    { if len(ss) == 0 { return 0, nil } head, tail := ss[0], ss[1:] n, err := io.WriteString(w, head) if err != nil { return n, err // どうやってテストする? } if len(tail) > 0 { m, err := io.WriteString(w, sep) n += m if err != nil { return n, err // どうやってテストする? } } m, err := WriteJoined(w, tail, sep) return n + m, err }
  5. とりあえず専用のWriterを作る var errOnWriteCall = errors.New("write call error") type ErrOnCallWriter struct

    { w io.Writer call int count int } func NewErrOnWriteCall(w io.Writer, call int) *ErrOnCallWriter { return &ErrOnCallWriter{w, call, 0} } func (w *ErrOnCallWriter) Write(p []byte) (int, error) { w.count++ if w.count == w.call { return 0, errOnWriteCall } return w.w.Write(p) }
  6. 要素の書き込みでエラーを上げるテスト func TestJoinWriterElementError(t *testing.T) { buf := &bytes.Buffer{} w :=

    NewErrOnWriteCall(buf, 1) in := []string{"foo", "bar", "baz"} sep := "," exp := "" n, err := WriteJoined(w, in, sep) if n != len(exp) || !errors.Is(err, errOnWriteCall) { t.Errorf( "WriteJoined(...) = %d, %v: wanted %d, %v", n, err, len(exp), errOnWriteCall, ) } got := buf.String() if got != exp { t.Errorf( "unexpected bytes written (-exp, +got): %v", diff.LineDiff(exp, got).Join(), ) } }
  7. 区切り文字で書き込みでエラーを上げるテスト func TestJoinWriterSepError(t *testing.T) { buf := &bytes.Buffer{} w :=

    NewErrOnWriteCall(buf, 2) in := []string{"foo", "bar", "baz"} sep := "," exp := "foo" n, err := WriteJoined(w, in, sep) if n != len(exp) || !errors.Is(err, errOnWriteCall) { t.Errorf( "WriteJoined(...) = %d, %v: wanted %d, %v", n, err, len(exp), errOnWriteCall, ) } got := buf.String() if got != exp { t.Errorf( "unexpected bytes written (-exp, +got): %v", diff.LineDiff(exp, got).Join(), ) } }
  8. Not 汎用 but 抽象 できないといけないこと Write(p []byte) (int, error) メソッドを持つオブジェクトを簡単につくれる。

    → io.Writer を関数型でモックする。 // Writer represents a mocked Write call. type Writer func(p []byte) (int, error) // Write simulates a Write call by using the provided mock function. func (write Writer) Write(p []byte) (int, error) { return write(p) }
  9. n回目の呼び出しでエラーを返す func CallCountWriter(write func(i int, p []byte) (int, error)) Writer

    { i := 0 return func(p []byte) (int, error) { i++ return write(i, p) } } func ErrOnCallWriter(count int, err error) Writer { return CallCountWriter(func(i int, p []byte) (int, error) { if count == i { return 0, err } return len(p), nil }) }
  10. mバイト書き込んだらエラーを返す func ByteCountWriter(write func(i int, p []byte) (int, error)) Writer

    { i := 0 return func(p []byte) (int, error) { n, err := write(i, p) i += len(p) return n, err } } func ErrOnByteWriter(count int, err error) Writer { return ByteCountWriter(func(i int, p []byte) (int, error) { n := count - i switch { case len(p) < n: return len(p), nil case 0 < n: return n, err default: return 0, err } }) }
  11. n回目のReadでエラーを返す(一部抜粋) type ReadMocker struct { r io.Reader } func NewReadMocker(r

    io.Reader) ReadMocker { return ReadMocker{r} } func (mock ReadMocker) ErrOnCall(count int, err error) Reader { return CallCountReader(func(i int, p []byte) (int, error) { n, rerr := mock.r.Read(p) if i == count { return n, err } return n, rerr }) }