dongtao1262 2017-05-11 09:55
浏览 112
已采纳

在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.

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) }
  • 写回答

1条回答 默认 最新

  • dqjgf0982 2017-05-11 11:42
    关注

    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
    }
    

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

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

    What we will need

    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.

    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.

    Preparing / Setting up the mocked filesystem

    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".

    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.

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

    And the test(ing) code

    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)
        }
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 fluent的在模拟压强时使用希望得到一些建议
  • ¥15 STM32驱动继电器
  • ¥15 Windows server update services
  • ¥15 关于#c语言#的问题:我现在在做一个墨水屏设计,2.9英寸的小屏怎么换4.2英寸大屏
  • ¥15 模糊pid与pid仿真结果几乎一样
  • ¥15 java的GUI的运用
  • ¥15 Web.config连不上数据库
  • ¥15 我想付费需要AKM公司DSP开发资料及相关开发。
  • ¥15 怎么配置广告联盟瀑布流
  • ¥15 Rstudio 保存代码闪退