在Golang中测试文件系统的示例代码

2 unit-testing go

我正在尝试为将与文件系统交互的功能编写单元测试,并且希望能够在测试期间模拟文件系统。

给出下面的代码作为问题的答案,您可以在其中创建一个在测试期间使用的文件系统接口,但是我对Go并不陌生,并且正在努力寻找如何使用它。

请问有人能够提供一个示例说明如何在测试中使用该接口吗?

var fs fileSystem = osFS{}

type fileSystem interface {
    Open(name string) (file, error)
    Stat(name string) (os.FileInfo, error)
}

type file interface {
    io.Closer
    io.Reader
    io.ReaderAt
    io.Seeker
    Stat() (os.FileInfo, error)
}

// osFS implements fileSystem using the local disk.
type osFS struct{}

func (osFS) Open(name string) (file, error)        { return os.Open(name) }
func (osFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
Run Code Online (Sandbox Code Playgroud)

小智 9

另一种选择是testing/fstest包:

package main
import "testing/fstest"

func main() {
   m := fstest.MapFS{
      "hello.txt": {
         Data: []byte("hello, world"),
      },
   }
   b, e := m.ReadFile("hello.txt")
   if e != nil {
      panic(e)
   }
   println(string(b) == "hello, world")
}
Run Code Online (Sandbox Code Playgroud)

https://golang.org/pkg/testing/fstest


icz*_*cza 5

您一定不能忘记的一件重要事情:只有与文件系统交互的代码filesystem使用fs全局变量(或filesystem测试代码可以使用的其他一些值)通过上述提供的文件系统接口()来模拟文件系统时,才可以对文件系统进行模拟更改,例如传递的fs参数)。

让我们看一个这样的示例函数:

func getSize(name string) (int64, error) {
    stat, err := fs.Stat(name)
    if err != nil {
        return 0, err
    }
    return stat.Size(), nil
}
Run Code Online (Sandbox Code Playgroud)

这个简单的getSize()函数返回由文件名指定的文件的大小,如果filesystem.Stat()失败则返回错误(返回错误)。

现在让我们编写一些完全覆盖此getSize()功能的单元测试。

我们将需要什么

我们需要一个的模拟版本filesystem,经过模拟,以便它实际上不与文件系统交互,但是在调用方法时filesystemfilesystem.Stat()在我们的例子中)返回敏感数据。为了最简单地模拟filesystem(或任何接口),我们将嵌入filesystemmockedFS,因此我们将“继承”其所有方法,并且只需要模拟可测试代码实际使用的内容。请注意,调用其他方法会导致运行时出现恐慌,因为我们实际上不会nil对此嵌入式对象提供合理的非值filesystem,但是出于测试的目的,它不是必需的。

由于filesystem返回的值os.FileInfo(除错误外)是一个接口(并且其实现未从os包中导出),因此我们还需要模拟os.FileInfo。这将是mockedFileInfo,我们将执行此操作与模拟非常相似filesystem:我们将嵌入接口类型os.FileInfo,因此实际上我们只需要实现FileInfo.Size(),因为这是可测试getSize()函数调用的唯一方法。

准备/设置模拟文件系统

一旦有了模拟的类型,就必须进行设置。由于getSize()使用全局fs变量与文件系统进行交互,因此我们需要mockedFS为该全局fs变量分配我们的值。在执行此操作之前,建议保存其旧值,并在完成测试:“清理”后正确恢复旧值。

由于我们完全希望进行测试getSize()(包括错误情况),因此我们mockedFS可以控制是否应返回错误,并在不需要任何错误的情况下告诉它返回什么,这使我们具有了强大的能力。

在进行测试时,我们可以操纵的“状态” mockedFS以使其行为符合我们的需求。

和测试代码

事不宜迟,完整的测试代码:

type mockedFS struct {
    // Embed so we only need to "override" what is used by testable functions
    osFS

    reportErr  bool  // Tells if this mocked FS should return error in our tests
    reportSize int64 // Tells what size should Stat() report in our test
}

type mockedFileInfo struct {
    // Embed this so we only need to add methods used by testable functions
    os.FileInfo
    size int64
}

func (m mockedFileInfo) Size() int64 { return m.size }

func (m mockedFS) Stat(name string) (os.FileInfo, error) {
    if m.reportErr {
        return nil, os.ErrNotExist
    }
    return mockedFileInfo{size: m.reportSize}, nil
}

func TestGetSize(t *testing.T) {
    oldFs := fs
    // Create and "install" mocked fs:
    mfs := &mockedFS{}
    fs = mfs
    // Make sure fs is restored after this test:
    defer func() {
        fs = oldFs
    }()

    // Test when filesystem.Stat() reports error:
    mfs.reportErr = true
    if _, err := getSize("hello.go"); err == nil {
        t.Error("Expected error, but err is nil!")
    }

    // Test when no error and size is returned:
    mfs.reportErr = false
    mfs.reportSize = 123
    if size, err := getSize("hello.go"); err != nil {
        t.Errorf("Expected no error, got: %v", err)
    } else if size != 123 {
        t.Errorf("Expected size %d, got: %d", 123, size)
    }
}
Run Code Online (Sandbox Code Playgroud)