我如何模拟对ioutil.ReadFile的调用呢?

时间:2014-01-04 17:03:15

标签: testing go

我有以下功能:

func ObtainTranslationStringsFile(path string) ([]string, error) {
    if contents, err := ioutil.ReadFile(path); err != nil {
        return ObtainTranslationStrings(string(contents))
    } else {
        return nil, err
    }
}

我需要模拟ioutil.ReadFile,但我不知道该怎么做。有可能吗?

3 个答案:

答案 0 :(得分:10)

如果你想嘲笑这个,有几种方法可以解决这个问题。第一个也许是最简单的是改变使用ioutil.ReadFile,而是调用带有io.Reader接口的ioutil.ReadAll。按this method注入自己的io.Reader /文件系统实现非常容易。

如果您不想更改方法签名,则另一个选项是不使用ioutil,而是声明ReadFile的函数替换。使用对象方法而不是函数注入会更容易,但它可以做到。它只依赖于包级别变量魔法,这对某些人来说可能看起来很令人反感。请参阅下面介绍的选项:

package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    _ "log"
)

type ReadFile func(filename string) ([]byte, error)
// myReadFile is a function variable that can be reassigned to handle mocking in option #2
var myReadFile = ioutil.ReadFile

type FakeReadFiler struct {
    Str string
}

// here's a fake ReadFile method that matches the signature of ioutil.ReadFile
func (f FakeReadFiler) ReadFile(filename string) ([]byte, error) {
    buf := bytes.NewBufferString(f.Str)
    return ioutil.ReadAll(buf)
}

func main() {
    payload := "PAYLOAD"
    path := "/dev/nul"
    buf := bytes.NewBufferString(payload)

    // option 1 is more elegant, but you have to change the method signature to take an io.Reader
    result1, err := ObtainTranslationStringsFileChoice1(buf)
    fmt.Printf("ObtainTranslationStringsFileChoice1 == %#v, %#v\n", result1, err)

    // option 2 keeps the method signature, but allows you to reassign a the myReadFile variable to change the method behavior for testing
    fake := FakeReadFiler{Str: payload}
    myReadFile = fake.ReadFile
    result2, err := ObtainTranslationStringsFileChoice2(path)
    fmt.Printf("ObtainTranslationStringsFileChoice2 == %#v, %#v\n", result2, err)
}

func ObtainTranslationStringsFileChoice1(rdr io.Reader) ([]string, error) {
    if contents, err := ioutil.ReadAll(rdr); err == nil {
        return []string{string(contents)}, nil
    } else {
        return nil, err
    }
}

func ObtainTranslationStringsFileChoice2(path string) ([]string, error) {
    if contents, err := myReadFile(path); err == nil {
        return []string{string(contents)}, nil
    } else {
        return nil, err
    }
}

此处的游乐场版本:sandbox

如果你想变得更复杂,我建议制作一个完整的文件系统模拟。这是我通常做的事情,并不像听起来那么困难:https://talks.golang.org/2012/10things.slide#8。在您的示例中,您没有使用结构和接口,这使得这种模拟更加强大。

答案 1 :(得分:0)

如果您需要模拟io.Reader,请使用https://github.com/stretchr/testify

解决方案

在你的包中声明

type ioReader interface {
    io.Reader
}

这只需要电话嘲弄你需要这样的界面,它会生成相应的模拟。

然后生成模拟

go get github.com/vektra/mockery/.../
mockery -inpkg -all

然后在您的测试代码中,您可以这样做

str := "some string"
r := &mockIoReader{}
r.
    On("Read", mock.Anything).
    Run(func(args mock.Arguments) {
        bytes := args[0].([]byte)
        copy(bytes[:], str)
    }).
    Return(len(str), nil)

答案 2 :(得分:-2)

不要模仿ioutil.ReadFile。一旦你嘲笑它,测试ObtainTranslationStringsFile将不会测试ObtainTranslationStrings测试。如果您需要测试ObtainTranslationStringsFile,请创建一个临时文件并将其路径传递给ObtainTranslationStringsFile。这样,您将实际测试该功能添加的功能。