我正在尝试为将与文件系统交互的功能编写单元测试,并且希望能够在测试期间模拟文件系统。
给出下面的代码作为该问题的答案,您可以在其中创建一个在测试期间使用的文件系统接口,但是我对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
您一定不能忘记的一件重要事情:只有与文件系统交互的代码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,经过模拟,以便它实际上不与文件系统交互,但是在调用方法时filesystem(filesystem.Stat()在我们的例子中)返回敏感数据。为了最简单地模拟filesystem(或任何接口),我们将嵌入filesystem到mockedFS,因此我们将“继承”其所有方法,并且只需要模拟可测试代码实际使用的内容。请注意,调用其他方法会导致运行时出现恐慌,因为我们实际上不会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)
| 归档时间: |
|
| 查看次数: |
3308 次 |
| 最近记录: |