【问题标题】:Mocking single methods in Go在 Go 中模拟单个方法
【发布时间】:2017-03-10 18:00:35
【问题描述】:

在 Go 中,如何模拟接口而不需要实现每个方法?假设我有一个Car 接口和一个实现该接口的Corolla 结构:

type Car interface {
    changeTire()
    startEngine()
    ....
    refuel()
}

type Corolla struct {
    ...
}

func (c Corolla) changeTire() {...}

func (c Corolla) startEngine() {...}

func (c Corolla) refuel() {...}

假设我还有一个依赖于CarGarage 结构:

type Garage struct {
    MyCar Car
}

func (g Garage) PrepareCarToDrive() {
    g.MyCar.changeTire()
    g.MyCar.refuel()
    g.MyCar.startEngine()
}

我想测试Garage,所以我创建了一个实现CarMockCar

type MockCar struct {
    ...
}

func (c MockCar) changeTire() {...}

func (c MockCar) startEngine() {...}

func (c MockCar) refuel() {...}

现在我有测试 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
    ...
}

我的问题是,我怎样才能让mockCar 每次测试都做不同的事情?我知道我可以为每个测试创建一个不同的 Car 模拟实现。但随着我向Car 添加更多方法,这将很快失控。

我来自 Java 背景,所以我正在寻找类似 Mockito 的东西,它可以让我模拟每次测试所需的方法。

在 Go 中执行此操作的最佳方法是什么?还是我错过了一些更基本的东西?

【问题讨论】:

    标签: testing go mocking


    【解决方案1】:

    如果您将接口类型本身嵌入到您的模拟结构中,那么您可以只实现您需要的方法。例如:

    type MockCar struct {
        Car
        ...
    }
    
    func (c MockCar) changeTire() {...}
    

    即使你的结构只显式地实现了changeTire,它仍然满足接口,因为Car 字段提供了其余部分。只要您不尝试调用任何未实现的方法(这将导致恐慌,因为Carnil),这将起作用

    【讨论】:

    • 这就是我忘记的方式!虽然值得注意,但您可以将 Corolla 之类的东西放入接口中,以获得您不覆盖的东西的默认实现。也就是说,这不提供定义每个测试函数覆盖的能力,尽管它可以与我下面的函数字段样式结合使用。
    • 非常感谢。一个有点相关的问题:IIUC 我无法设置嵌入 inside 我的测试方法。如果我有几个测试方法和几个 Mocks,捆绑它们的最佳方法是什么?使用 test + mock 对创建单独的 go 文件?
    • 我不确定我是否理解您的问题。您可能想发布一个包含一些详细信息的全新问题。
    • 谢谢!这就是我一直在寻找的。​​span>
    【解决方案2】:

    最简单的方法是使用一些基本实现作为测试结构的嵌入,并且只覆盖您正在测试的方法。使用您的类型的示例:

    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
    

    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
        ...
    }
    

    https://play.golang.org/p/lf7ny-lUCS

    当您尝试使用另一种类型的方法作为默认实现时,这种风格的用处会有所降低,但如果您有一些可用作默认或测试实现的独立函数,或者如果您没有专门模拟的函数可以接受微不足道的返回(例如上面示例中模拟的startEngine() 的行为,因为startEngineFunc 字段为nil,所以在调用时它什么都不做)。

    如果相关函数字段为 nil,您还可以将默认实现(如对 (Corolla{}).startEngine() 的调用)烘焙到模拟方法中。这使您可以两全其美,具有默认的非平凡实现仅通过更改相关函数字段即可在模拟上随意热交换实现的能力。

    【讨论】:

      猜你喜欢
      • 2019-02-18
      • 2021-09-30
      • 2012-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-29
      相关资源
      最近更新 更多