【问题标题】:Example code for testing the filesystem in Golang在 Golang 中测试文件系统的示例代码
【发布时间】:2017-10-10 06:17:20
【问题描述】:

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

下面的代码是 this 问题的答案,您可以在其中创建一个文件系统接口以在测试期间使用,但我是 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) }

【问题讨论】:

    标签: unit-testing go


    【解决方案1】:

    您不能忘记一件重要的事情:如果与文件系统交互的代码通过上述文件系统接口 (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
    }
    

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

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

    我们需要什么

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

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

    准备/设置模拟文件系统

    一旦我们有了模拟类型,我们就必须设置它们。由于getSize() 使用全局fs 变量与文件系统进行交互,我们需要将mockedFS 的值分配给这个全局fs 变量。在这样做之前,建议保存它的旧值,并在我们完成测试后正确恢复旧值:“cleanup”。

    由于我们完全想测试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)
        }
    }
    

    【讨论】:

    • 这是用于文件统计的,但是对于 openFile 来说呢?
    • @PritiBiyani 此示例显示如何模拟返回接口值 (os.FileInfo) 的 filesystem.Stat()。您在问如何模拟filesystem.Open(),它还返回一个接口值(file)。所以答案是:以同样的方式,但显然你必须“覆盖”filesystem.Open() 而不是filesytem.Stat(),显然你必须创建并返回一个mockedFile 而不是mockedFileInfo
    • 啊。谢谢。我在生产代码中的初始化如何?我原来的fs 会是什么?
    • @PritiBiyani 原始fs在链接问题中:stackoverflow.com/questions/16742331/…
    • @PritiBiyani 请阅读我发布的链接。答案就在那里。
    【解决方案2】:

    另一个选项是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")
    }
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-19
      • 1970-01-01
      • 1970-01-01
      • 2010-09-12
      • 2013-11-12
      • 1970-01-01
      相关资源
      最近更新 更多