在Go测试中模拟嵌入式结构

时间:2018-09-20 17:41:37

标签: unit-testing go

给出:

// FileReader interface for reading from a file
type FileReader interface {
    ReadFile(filename string) ([]byte, error)
}

type FileRead struct {}

// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
    return ioutil.ReadFile(fn)
}

type Dev struct {
    *FileRead
}

func NewDev() *Dev {
    frd := FileRead{}
    return &Dev{frd}
}

// Function that does some job
func (dev Dev) DoSomeStuff() {
    //...
    dev.ReadFile("file")
    //...
}

func main () {
    doer := NewDev()
    doer.DoSomeStuff()
}

在单元测试期间,应该模拟ReadFile操作。如何在测试中最好地做到这一点?

Dev结构可以改用FileReader接口,但随后不再使用结构嵌入,因此DoSomeStuff中的语法变得不太明显。

1 个答案:

答案 0 :(得分:-2)

如果您使用的是诸如Dargo之类的DI框架,则可以将一些实现FileReader的东西注入到dev中。在您的主代码中,您将绑定普通的FileReader,但是在测试中,您可以使用模拟FileReader。您的其余代码不应该知道区别。看起来像这样:

import (
    "github.com/jwells131313/dargo/ioc"
    "io/ioutil"
    "testing"
)

type FileReader interface {
    ReadFile(filename string) ([]byte, error)
}

type FileRead struct{}

// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
    return ioutil.ReadFile(fn)
}

type Dev struct {
    MyReader FileReader `inject:"FileReader"`
}

/* Not here anymore, but you can implement DargoInitializer
   if you need more initialization of Dev
func NewDev() *Dev {
    frd := FileRead{}
    return &Dev{frd}
}
*/

// Function that does some job
func (dev Dev) DoSomeStuff() {
    //...
    dev.MyReader.ReadFile("file")
    //...
}

var locator ioc.ServiceLocator

func initialize() error {
    l, err := ioc.CreateAndBind("Example", func(binder ioc.Binder) error {
        binder.Bind("Dev", &Dev{})
        binder.Bind("FileReader", &FileRead{})
        return nil
    })

    locator = l

    return err

}

func main() {
    initialize()

    raw, _ := locator.GetDService("Dev")
    doer := raw.(*Dev)

    doer.DoSomeStuff()
}

// Here is test code

type TestFileRead struct{}

// ReadFile is a mock that just returns a zero-length byte array
func (tfr TestFileRead) ReadFile(fn string) ([]byte, error) {
    return []byte{}, nil
}

func TestMe(t *testing.T) {
    initialize()

    ioc.BindIntoLocator(locator, func(binder ioc.Binder) error {
        binder.Bind("FileReader", &TestFileRead{}).Ranked(1)
        return nil
    })

    // Test Me!

}

在上面的示例中,“常规”文件阅读器以常规代码注入,但是在测试中,有一个具有更高等级的TestFileReader。因此,当您去参加开发测试时,它会被注入测试之一,而不是主代码中的测试。

希望这会有所帮助。