tir*_*r38 3 testing mocking go
在Go中,如何在无需实现每种方法的情况下模拟接口?假设我有一个Car接口和一个Corolla实现该接口的结构:
type Car interface {
changeTire()
startEngine()
....
refuel()
}
type Corolla struct {
...
}
func (c Corolla) changeTire() {...}
func (c Corolla) startEngine() {...}
func (c Corolla) refuel() {...}
Run Code Online (Sandbox Code Playgroud)
假设我还有一个Garage依赖于的结构Car:
type Garage struct {
MyCar Car
}
func (g Garage) PrepareCarToDrive() {
g.MyCar.changeTire()
g.MyCar.refuel()
g.MyCar.startEngine()
}
Run Code Online (Sandbox Code Playgroud)
而且我想测试Garage,所以我创建了一个MockCar实现Car
type MockCar struct {
...
}
func (c MockCar) changeTire() {...}
func (c MockCar) startEngine() {...}
func (c MockCar) refuel() {...}
Run Code Online (Sandbox Code Playgroud)
现在我有测试, PrepareCarToDrive我使用MockCar:
func TestGarage_PrepareCarToDrive_DoesSomething(t *testing.T) {
mockCar := MockCar{}
garageUnderTest := Garage{}
garageUnderTest.MyCar = mockCar
// some other setup
// when Garage calls mockCar.changeTire(), should do X
...
}
func TestGarage_PrepareCarToDrive_DoesSomethingElse(t *testing.T) {
mockCar := MockCar{}
garageUnderTest := Garage{}
garageUnderTest.MyCar = mockCar
// some other setup
// when Garage calls mockCar.changeTire(), should do Y
...
}
Run Code Online (Sandbox Code Playgroud)
我的问题是,我如何才能在mockCar每次测试中做不同的事情?我知道我可以Car为每个测试创建不同的模拟实现。但这会随着我向中添加更多方法而很快消失Car。
我来自Java背景,所以我正在寻找Mockito之类的东西,让我可以模拟每次测试所需的方法。
Go中执行此操作的最佳方法是什么?还是我缺少更根本的东西?
最简单的方法是使用一些基本实现作为测试结构的嵌入,并且仅覆盖您正在测试的方法。使用您的类型的示例:
type MockCar struct {
Corolla // embedded, so the method implementations of Corolla get promoted
}
// overrides the Corolla implementation
func (c MockCar) changeTire() {
// test stuff
}
// refuel() and startEngine(), since they are not overridden, use Corolla's implementation
Run Code Online (Sandbox Code Playgroud)
https://play.golang.org/p/q3_L1jf4hk
如果每个测试需要不同的实现,另一种方法是使用带有函数字段的模拟:
type MockCar struct {
changeTireFunc func()
startEngineFunc func()
....
refuelFunc func()
}
func (c MockCar) changeTire() {
if c.changeTireFunc != nil {
c.changeTireFunc()
}
}
func (c MockCar) startEngine() {
if c.startEngineFunc != nil {
c.startEngineFunc()
}
}
func (c MockCar) refuel() {
if c.refuelFunc != nil {
c.refuelFunc()
}
}
// test code
func TestGarage_PrepareCarToDrive_DoesSomething(t *testing.T) {
// let's say we require refuel(), but the default implementation is fine
// changeTire(), however, requires a mocked testing implementation
// and we don't need startEngine() at all
mockCar := MockCar{
changeTireFunc: func() {
// test functionality
},
refuelFunc: Corolla.refuel,
}
garageUnderTest := Garage{}
garageUnderTest.MyCar = mockCar
// some other setup
// when Garage calls mockCar.changeTire(), should do X
...
}
Run Code Online (Sandbox Code Playgroud)
https://play.golang.org/p/lf7ny-lUCS
当您尝试使用另一种类型的方法作为默认实现时,这种风格的用处有点小,但如果您有一些可以用作默认或测试实现的独立函数,或者如果一个简单的函数,则这种风格可能非常有用。对于您没有专门模拟的函数来说, return 是可以接受的(就像startEngine()上面示例中模拟的行为一样,它在调用时什么也不做,因为该startEngineFunc字段为零)。
(Corolla{}).startEngine()如果您愿意,如果相关函数字段为零,您还可以将默认实现(例如对 的调用)烘焙到模拟方法中。这使您可以两全其美,具有默认的非平凡实现,并且能够通过更改相关函数字段在模拟上随意热交换实现。
如果将接口类型本身嵌入到模拟结构中,则只能实现所需的方法。例如:
type MockCar struct {
Car
...
}
func (c MockCar) changeTire() {...}
Run Code Online (Sandbox Code Playgroud)
即使您的结构仅changeTire显式实现,它仍然满足该接口,因为该Car字段提供了其余部分。只要您不尝试调用任何未实现的方法(这会引起恐慌,因为Caris nil),此方法就起作用
| 归档时间: |
|
| 查看次数: |
654 次 |
| 最近记录: |