您一定不能忘记的一件重要事情:如果与文件系统交互的代码
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(或任何接口),我们将嵌入
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) }}


