goroutineのテストを同期的に行う

とある関数の評価値 ― 例えばファイルに文字列を書き込む ― をテストしたい場合、 その評価がgoroutine内だと、テスト側から実行しても、タイミングによって取得できないことがあります。 そこで、テスト側から評価するときにはchanelを渡す用にして、そのchanelに対して書き込むようにすることで同期処理できるようになります。

type SUTType struct {
  ...
  Out io.WriteCloser
  ...
}

func (o *SUTType) SUT() {
  ...
  go func() {
    o.Out.Write("aqours")
  }()
  ...
}

テスト対象がこんな感じのコードになってたりする。 のでこの、 Out をすげ替えてあげればいい。

func TestSUT(t *testing.T) {
  s := &SUTType{Out: &DummyOut{}}
  
  s.SUT()

  donewait := make(chan struct{}{})
  var result string
  go func() {
    result := <-out.Buf
    donewait <- struct{}{}
  }()

  <-donewait

  if result != "aqours" {
    t.Error("should be aqours")
  }
}

type DummyOut struct {
  Buf chan []byte
}

func (o *DummyOut) Write(p []byte) (int, error) {
  o.Buf <- p
  return 0, nil
}

func (o *DummyOut) Close() error {
  return nil
}

chanelを使うことで同期処理が可能になる、ということと、テスト可能なコードにする場合は、 インタフェースを持つことでテスト側で柔軟なコード ― 上記の例だと、 SUTType のOutの型が os.File だと途端にテストが面倒になる ― にできる典型例でした。