在 Golang 中测试文件系统的示例代码 [英] Example code for testing the filesystem in Golang

查看:18
本文介绍了在 Golang 中测试文件系统的示例代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to write a unit test for a function that will interact with the filesystem and I'd like to be able to mock the filesystem during testing.

下面的代码是对这个问题的回答,您将在其中创建一个文件系统接口以在测试期间使用,但我是 Go 的新手并且正在努力弄清楚如何使用它.

The code below was given as the answer to this question, where you would create a filesystem interface to use during testing, but I'm new to Go and am struggling to figure out how to use it.

有人能提供一个例子来说明如何在测试中使用这个接口吗?

Would someone be able to provide an example of how this interface would be used in a test please?

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) }

推荐答案

你不能忘记的一件重要事情:如果与文件系统交互的代码通过上述文件系统接口模拟文件系统,你只能模拟文件系统(filesystem),使用 fs 全局变量(或测试代码可以更改的其他一些 filesystem 值,例如传递的 fs 参数).

One important thing you must not forget: you can only mock the file system if the code that interacts with the file system does so via the above presented file system interface (filesystem), using the fs global variable (or some other filesystem value that the test code can change, e.g. a passed fs parameter).

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

Let's see such an example function:

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

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

This simple getSize() function returns the size of a file specified by its name, returning the error if filesystem.Stat() fails (returns an error).

现在让我们编写一些单元测试来完全覆盖这个 getSize() 函数.

And now let's write some unit tests that fully cover this getSize() function.

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

We need a mocked version of filesystem, mocked so that it does not actually interact with the filesystem, but returns sensible data when methods of filesystem are called (filesystem.Stat() in our case). To easiest mock filesystem (or any interface), we will embed filesystem in our mockedFS, so we "inherit" all its methods, and we will only need to mock what is actually used by the testable code. Note that calling other methods would result in runtime panic, as we won't really give a sensible, non-nil value to this embedded filesystem, but for the sake of tests it is not needed.

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

Since filesystem returns a value of os.FileInfo (besides an error), which is an interface (and its implementation is not exported from the os package), we will also need to mock os.FileInfo. This will be mockedFileInfo, and we will do it very similarly to mocking filesystem: we'll embed the interface type os.FileInfo, so actually we'll only need to implement FileInfo.Size(), because that is the only method called by the testable getSize() function.

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

Once we have the mocked types, we have to set them up. Since getSize() uses the global fs variable to interact with the filesystem, we need to assign a value of our mockedFS to this global fs variable. Before doing so it's recommended to save its old value, and properly restore the old value once we're done with the test: "cleanup".

由于我们完全想测试 getSize()(包括错误情况),我们为 mockedFS 提供了控制它是否应该返回错误的能力,并且还能够告诉它返回什么,以防我们不希望出现任何错误.

Since we fully want to test getSize() (including the error case), we armour our mockedFS with the ability to control whether it should return an error, and also the ability to tell it what to return in case we don't want any errors.

在进行测试时,我们可以操纵 mockedFS 的状态",使其行为符合我们的需要.

When doing the tests, we can manipulate the "state" of the mockedFS to bend its behavior to our needs.

不用多说,完整的测试代码:

Without further ado, the full testing code:

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)
    }
}

这篇关于在 Golang 中测试文件系统的示例代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆