テストのためのIO抽象
先日のGoken vol.14でJxckさんから良いことを聞いたのでメモに残す。
定義
何はともかく定義を。
type rwcMock struct { bytes.Buffer Closed bool } func (m *rwcMock) Close() error { m.Closed = true return nil }
Goは、ある構造体の埋め込むことでその構造体のように振る舞え、そのオブジェクトに対してメソッドが定義されていたらインターフェースを満たすという機能がある。 bytes.Bufferの定義は、
```go:buffer.go type Buffer struct { // ... }
func (b *Buffer) Read(p []byte) (n int, err error) { // ... }
func (b *Buffer) Write(p []byte) (n int, err error) { // ... }
となり、これはio.ReadWriterインターフェースを満たす。 ```go:io.go type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer }
そしてrwcMockはio.ReadWriteCloserインターフェースを満たす。
```go:io.go type Closer interface { Close() error }
type ReadWriteCloser interface { Reader Writer Closer }
## いかに使えるのか ここでnet.Connとos.Fileの定義の抜粋を記す。 ```go:net.go type Conn interface { Read(b []byte) (n int, err error) Write(b []byte) (n int, err error) Close() error // ... }
```go:os.go type File struct { // ... }
func (f *File) Read(b []byte) (n int, err error) { // ... }
func (f *File) Write(p []byte) (n int, err error) { // ... }
func (f *File) Close() error { // ... } ```
一目見て分かるように、これらのオブジェクトはio.ReadWriteCloserインターフェースを満たす。
つまり入出力でこれらのオブジェクトを使う箇所で型をio.ReadWriteCloserにしておけば、テストダブルとしてrwcMockを使えることを意味する。
Pythonにおけるfileオブジェクトの代わりにStringIOオブジェクトを渡して、内容を検査するのと同様なことがGoでもできる。
補足
POSIXではもっとプリミティブなファイルデスクリプタ、型でいうとuintptrまで抽象化することが可能なのだが、現状のGoはソケットのファイルデスクリプタを公開しておらず、内部的にはnetFDとして定義しているのだが、そとからはos.Fileでしか操作することが出来ない。
補足2
ここまで書いてから気付いた。net.Connインターフェースには定義されていないが、TCP/IPやUnixドメインソケットのConnオブジェクトにFile()メソッドが定義されていて、os.Fileオブジェクトが返るようだ。 つまり、ソケットでできることはファイルオブジェクトででもできる。 どちらにせよrwcMockのテストにおける有用性は変わらない。