kakakakakku blog

Weekly Tech Blog: Keep on Learning!

testing/fstest: Go でファイルシステムに依存したテストを書く

Go で testing/fstest を使うと,ファイルシステムに依存したテストコードを書くときに実際のファイルシステムへの依存度を減らせる.テストコードを書こうとすると,テスト項目(バリエーション)ごとにファイルを用意する必要があるため,ファイルの管理が面倒になることもある.最近使う機会があって簡単にまとめておく📝

pkg.go.dev

io/fs interface と testing/fstest.MapFS{}

testing/fstestfstest.MapFS{} を使うと任意のファイルで構成されるファイルシステム(仕組みとしてはインメモリとドキュメントに書いてある)を作れる.そして,ファイルシステムを抽象化した io/fs.FS interface を満たすオブジェクトになっているため,ビジネスロジック側で fs.FS を受け取るように実装しておけば,テストコードでは fstest のオブジェクトを渡せる.

pkg.go.dev

fs := fstest.MapFS{
    "helloworld.md":  {Data: []byte("helloworld")},
    "helloworld2.md": {Data: []byte("helloworld2")},
    "helloworld3.md": {Data: []byte("helloworld3")},
    "helloworld4.md": {Data: []byte("helloworld4")},
    "helloworld5.md": {Data: []byte("helloworld5")},
}

サンプルコード

以下のように main.gomain_test.go を書いてみた.

main.go

実装に特に意味はなくサンプルではあるけど,"files ディレクトリのファイル数を返す" というロジックを考える.ポイントはファイル数を返す countFiles() 関数の引数として fs.FS interface を受け取るようにしているところ❗️

package main

import (
    "fmt"
    "io/fs"
    "os"
)

func countFiles(fsys fs.FS) (int, error) {
    files, err := fs.ReadDir(fsys, ".")
    if err != nil {
        return 0, err
    }
    return len(files), nil
}

func main() {
    count, err := countFiles(os.DirFS("files"))
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(count)
}

main_test.go

テストコード側では testing/fstest.MapFS{} を使って,テスト項目ごとにファイルシステムを作っている(ファイルなし・1ファイル・2ファイル).もっともっとテスト項目が増えることを想像すると fstest を使うメリットが感じられると思う✌

package main

import (
    "testing"
    "testing/fstest"
)

func TestCount(t *testing.T) {
    t.Run("File does not exist.", func(t *testing.T) {
        fs := fstest.MapFS{}
        want := 0
        got, _ := countFiles(fs)
        assertCount(t, got, want)
    })

    t.Run("One file exists.", func(t *testing.T) {
        fs := fstest.MapFS{
            "helloworld.md": {Data: []byte("helloworld")},
        }
        want := 1
        got, _ := countFiles(fs)
        assertCount(t, got, want)
    })

    t.Run("Two files exist.", func(t *testing.T) {
        fs := fstest.MapFS{
            "helloworld.md":  {Data: []byte("helloworld")},
            "helloworld2.md": {Data: []byte("helloworld2")},
        }
        want := 2
        got, _ := countFiles(fs)
        assertCount(t, got, want)
    })
}

func assertCount(t *testing.T, got, want int) {
    t.Helper()
    if got != want {
        t.Errorf("got %d, want %d", want, got)
    }
}

まとめ

testing/fstest は便利だから覚えておこう〜❗️