想象一下这样一个工具:它会遍历一个目录,并返回所能找到的所有以.go结尾的文件名称。如果此工具不能和文件系统交互,那么它将毫无用处。现在,假设有一个 web 应用,它内嵌了一些静态文件,比如images, templates, and style sheets等等。那这个 Web 应用程序在访问这些相关assets时应使用虚拟文件系统,而不是真实文件系统。
funcGoFiles(root string) ([]string, error) { var data []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error)error { if err != nil { return err } base := filepath.Base(path) for _, sp := range SkipPaths { // if the name of the folder has a prefix listed in SkipPaths // then we should skip the directory. // e.g. node_modules, testdata, _foo, .git if strings.HasPrefix(base, sp) { return filepath.SkipDir } }
for _, s := range SkipPaths { // ex: ./.git/git.go // ex: ./node_modules/node_modules.go names = append(names, filepath.Join(s, s+".go")) }
for _, f := range names { if err := os.MkdirAll(filepath.Join(dir, filepath.Dir(f)), 0755); err != nil { b.Fatal(err) } if err := ioutil.WriteFile(filepath.Join(dir, f), nil, 0666); err != nil { b.Fatal(err) } }
list, err := GoFiles(dir)
if err != nil { b.Fatal(err) }
lexp := 2 lact := len(list) if lact != lexp { b.Fatalf("expected list to have %d files, but got %d", lexp, lact) }
sort.Strings(list)
exp := []string{"foo.go", "web/routes.go"} for i, a := range list { e := exp[i] if !strings.HasSuffix(a, e) { b.Fatalf("expected %q to match expected %q", list, exp) } }
funcBenchmarkGoFilesExistingFiles(b *testing.B) { for i := 0; i < b.N; i++ {
list, err := GoFiles("./testdata/scenario1")
if err != nil { b.Fatal(err) }
lexp := 2 lact := len(list) if lact != lexp { b.Fatalf("expected list to have %d files, but got %d", lexp, lact) }
sort.Strings(list)
exp := []string{"foo.go", "web/routes.go"} for i, a := range list { e := exp[i] if !strings.HasSuffix(a, e) { b.Fatalf("expected %q to match expected %q", list, exp) } }
funcGoFilesFS(root string, sys fs.FS) ([]string, error) { var data []string
err := fs.WalkDir(sys, ".", func(path string, de fs.DirEntry, err error)error { if err != nil { return err }
base := filepath.Base(path) for _, sp := range SkipPaths { // if the name of the folder has a prefix listed in SkipPaths // then we should skip the directory. // e.g. node_modules, testdata, _foo, .git if strings.HasPrefix(base, sp) { return filepath.SkipDir } }
type FS interface { // Open opens the named file. // // When Open returns an error, it should be of type *PathError // with the Op field set to "open", the Path field set to name, // and the Err field describing the problem. // // Open should reject attempts to open names that do not satisfy // ValidPath(name), returning a *PathError with Err set to // ErrInvalid or ErrNotExist. Open(name string) (File, error) }
// ReadDir reads the contents of the directory and returns // a slice of up to n DirEntry values in directory order. // Subsequent calls on the same file will yield further DirEntry values. // // If n > 0, ReadDir returns at most n DirEntry structures. // In this case, if ReadDir returns an empty slice, it will return // a non-nil error explaining why. // At the end of a directory, the error is io.EOF. // // If n <= 0, ReadDir returns all the DirEntry values from the directory // in a single slice. In this case, if ReadDir succeeds (reads all the way // to the end of the directory), it returns the slice and a nil error. // If it encounters an error before the end of the directory, // ReadDir returns the DirEntry list read until that point and a non-nil error.
funcBenchmarkGoFilesFS(b *testing.B) { for i := 0; i < b.N; i++ { files := MockFS{ // ./foo.go NewFile("foo.go"), // ./web/routes.go NewDir("web", NewFile("routes.go")), }
for _, s := range SkipPaths { // ex: ./.git/git.go // ex: ./node_modules/node_modules.go files = append(files, NewDir(s, NewFile(s+".go"))) }
mfs := MockFS{ // ./ NewDir(".", files...), }
list, err := GoFilesFS("/", mfs)
if err != nil { b.Fatal(err) }
lexp := 2 lact := len(list) if lact != lexp { b.Fatalf("expected list to have %d files, but got %d", lexp, lact) }
sort.Strings(list)
exp := []string{"foo.go", "web/routes.go"} for i, a := range list { e := exp[i] if e != a { b.Fatalf("expected %q to match expected %q", list, exp) } }
虽然本文介绍了如何使用新的io/fs包来增强我们的测试,但这只是该包的冰山一角。比如,考虑一个文件转换管道,该管道根据文件的类型在文件上运行转换程序。再比如,将.md文件从Markdown转换为HTML,等等。使用io/fs包,您可以轻松创建带有接口的管道,并且测试该管道也相对简单。 Go 1.16有很多令人兴奋的地方,但是,对我来说,io/fs包是最让我兴奋的一个。