英文:
How to write mock for structs in Go
问题
我想为Transport
函数编写一个单元测试,需要模拟CarFactory
和Car
结构体。请看以下代码:
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++中,我可以定义CarFactoryMock
和CarMock
,它们继承自CarFactory
和Car
,然后重写MakeCar()
方法返回一个CarMock
对象。
class CarMock extends Car {
public void Run() {...}
}
class CarFactoryMock extends CarFactory {
public Car MakeCar() { return new CarMock(); }
}
Transport(new CarFactoryMock())
在Go语言中,我该如何实现这个功能?
请注意,我可以更改Transport
函数的原型和源代码,但必须保持CarFactory
和Car
不变,因为它们来自第三方包。
英文:
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})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论