How to write mock for structs in Go

huangapple go评论126阅读模式
英文:

How to write mock for structs in Go

问题

我想为Transport函数编写一个单元测试,需要模拟CarFactoryCar结构体。请看以下代码:

package main

type Car struct {
    Name string
}

func (h Car) Run() { ... }

type CarFactory struct {}

func (e CarFactory) MakeCar() Car {
    return Car{}
}

func Transport(cf CarFactory) {
    ...
    car := cf.MakeCar()
    car.Run()
    ...
}

在其他面向对象的语言如Java、C#或C++中,我可以定义CarFactoryMockCarMock,它们继承自CarFactoryCar,然后重写MakeCar()方法返回一个CarMock对象。

class CarMock extends Car {
    public void Run() {...}
}

class CarFactoryMock extends CarFactory {
    public Car MakeCar() { return new CarMock(); }
}

Transport(new CarFactoryMock())

在Go语言中,我该如何实现这个功能?

请注意,我可以更改Transport函数的原型和源代码,但必须保持CarFactoryCar不变,因为它们来自第三方包。

英文:

I want to write a unit test for the Transport function which will require mocking CarFactory and Car structs. See the following code:

package main

type Car struct {
    Name string
}

func (h Car) Run() { ... }

type CarFactory struct {}

func (e CarFactory) MakeCar() Car {
    return Car{}
}

func Transport(cf CarFactory) {
    ...
    car := cf.MakeCar()
    car.Run()
    ...
}

In other OOP languages like Java, C# or C++, I can just define CarFactoryMock and CarMock that extend CarFactory and Car then override MakeCar() method to return a CarMock object

class CarMock extends Car {
    public Run() {...}
}

class CarFactoryMock extends CarFactory {
    public Car MakeCar() { return new CarMock(); }                                                                                                                                                                                        
}

Transport(new CarFactoryMock())

How do I achieve this in Go?

Note that I can change prototype and source code of Transport function, but must keep CarFactory and Car the same since they are taken from a 3rd package


The last code snippet was about Human and Employee, which lead to confusion`.

答案1

得分: 35

在Go语言中,模拟一个结构体需要比其他支持完全后期绑定的面向对象编程语言写更多的代码。

由于以下代码是从第三方获取的,所以必须保持不变:

type Car struct {
    Name string
}

func (c Car) Run() { 
    fmt.Println("Real car " + c.Name + " is running")
}

type CarFactory struct {}

func (cf CarFactory) MakeCar(name string) Car {
    return Car{name}
}

由于Go语言只支持接口的后期绑定,所以我必须让Transport函数接收一个接口作为参数,而不是一个结构体:

type ICar interface {
    Run()
}

type ICarFactory interface {
    MakeCar(name string) ICar
}

func Transport(cf ICarFactory) {
    ...
    car := cf.MakeCar("lamborghini")
    car.Run()
    ...
}

以下是模拟的代码:

type CarMock struct {
    Name string
}

func (cm CarMock) Run() {
    fmt.Println("Mocking car " + cm.Name + " is running")
}

type CarFactoryMock struct {}
func (cf CarFactoryMock) MakeCar(name string) ICar {
    return CarMock{name}
}

现在我可以轻松使用模拟对象Transport(CarFactoryMock{})。但是当我尝试调用真实的方法Transport(CarFactory{})时,Go编译器会显示以下错误:

cannot use CarFactory literal (type CarFactory) as type ICarFactory in argument to Transport:
    CarFactory does not implement ICarFactory (wrong type for MakeCar method)
        have MakeCar(string) Car
        want MakeCar(string) ICar

正如错误信息所说,接口中的MakeCar函数返回的是ICar,但是真实的MakeCar返回的是Car。Go语言不允许这样做。为了解决这个问题,我不得不定义一个包装器来手动将Car转换为ICar

type CarFactoryWrapper struct {
    CarFactory
}

func (cf CarFactoryWrapper) MakeCar(name string) ICar {
    return cf.CarFactory.MakeCar(name)
}

现在你可以这样调用Transport函数:Transport(CarFactoryWrapper{CarFactory{}})

这是可工作的代码https://play.golang.org/p/6YyeZP4tcC

英文:

It takes more code to mock a struct in Go than other OOP languages that support full late binding.

This code must remain untouched since its taken from a 3rd party:

type Car struct {
    Name string
}

func (c Car) Run() { 
	fmt.Println("Real car " + c.Name + " is running")
}

type CarFactory struct {}

func (cf CarFactory) MakeCar(name string) Car {
    return Car{name}
}

Since Go only supports late binding on interface, I had to make Transport receive an interface as a parameter instead of a struct:

type ICar interface {
	Run()
}

type ICarFactory interface {
	MakeCar(name string) ICar
}

func Transport(cf ICarFactory) {
    ...
    car := cf.MakeCar("lamborghini")
    car.Run()
    ...
}

And here are the mocks:

type CarMock struct {
	Name string
}

func (cm CarMock) Run() {
	fmt.Println("Mocking car " + cm.Name + " is running")
}

type CarFactoryMock struct {}
func (cf CarFactoryMock) MakeCar(name string) ICar {
    return CarMock{name}
}

Now I can easily use the mock Transport(CarFactoryMock{}). But when I try to call the real method Transport(CarFactory{}), the go compiler shows me the following errors:

cannot use CarFactory literal (type CarFactory) as type ICarFactory in argument to Transport:
    CarFactory does not implement ICarFactory (wrong type for MakeCar method)
        have MakeCar(string) Car
        want MakeCar(string) ICar

As the message says, MakeCar function from the interface returns an ICar, but the real MakeCar returns a Car. Go doesn't allow that. To walk around this problem I had to define a wrapper to manually convert Car to ICar.

type CarFactoryWrapper struct {
	CarFactory
}

func (cf CarFactoryWrapper) MakeCar(name string) ICar {
	return cf.CarFactory.MakeCar(name)
}

Now you can call the Transport function like this: Transport(CarFactoryWrapper{CarFactory{}}).

Here is the working code https://play.golang.org/p/6YyeZP4tcC.

答案2

得分: 13

你使用了一个接口。

type Employee interface {
    GetHuman() Human
}

type RealEmployee struct {
    Company string
    h Human
}

func (e RealEmployee) GetHuman() Human {
    return e.h
}

// 使用真实的员工调用 Hire
Hire(RealEmployee{h: RealHuman})

Hire 方法接受接口 Employee,因此你可以在测试中编写一个 MockEmployee 结构体。

func Hire(e Employee) {
    ...
    h := e.GetHuman()
    fmt.Println(h.Name)
    ...
}

// 模拟的员工实例
type MockEmployee struct {
    Company string
    h Human
}

func (m MockEmployee) GetHuman() Human {
    return m.h
}

// 使用模拟的员工调用 Hire 进行测试
Hire(MockEmployee{h: MockHuman})
英文:

You use an interface.

type Employee interface {
    GetHuman() Human
}

type RealEmployee struct {
    Company string
    h Human
}

func (e RealEmployee) GetHuman() Human {
    return e.h
}

// Call Hire with real employee
Hire(RealEmployee{h: RealHuman})

Hire method accepts the interface Employee, then you can write one MockEmployee struct in your tests.

func Hire(e Employee) {
    ...
    h := e.GetHuman()
    fmt.Println(h.Name)
    ...
}

// Mock Employee instance
type MockEmployee struct {
    Company string
    h Human
}

func (m MockEmployee) GetHuman() Human {
    return m.h
}

// Call Hire to test with mock employee
Hire(MockEmployee{h: MockHuman})

huangapple
  • 本文由 发表于 2016年12月9日 12:45:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/41053280.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定