如何在Go中对结构的特定方法执行单元测试

时间:2017-10-13 21:43:31

标签: unit-testing go

假设我有一个结构Car并且它有一些我想测试的方法。例如IgniteEngineSwitchGeardrive。如您所见,drive取决于其他方法。我需要一种模仿IgniteEngineSwitchGear的方法。

我认为我应该使用界面,但我不太明白如何实现它。

假设Car现在是一个界面

type Car interface {
    IgniteEngine()
    SwitchGear()
    Drive()
}

我可以为MockCarIgniteEngine创建SwitchGear和两个模拟函数,但现在我如何测试Drive的源代码?

我是否将源代码复制并粘贴到模拟对象中?这看起来很傻。我是否对如何进行模拟有错误的想法?嘲讽只在依赖注入时才起作用吗?

现在如果Drive实际上依赖于外部库(如数据库或消息代理系统)会怎样?

谢谢

2 个答案:

答案 0 :(得分:2)

基本上,也许,这是一个很大的杂耍游戏,涉及不同程度地应用接口,封装和抽象。

通过使Car成为一个接口并应用依赖注入,它可以让您的测试轻松地运用依赖汽车的组件。

func GoToStore(car Honda) {
    car.IgniteEngine()
    car.Drive()
}

这个愚蠢的功能驱使本田到商店。它与Honda类紧密耦合。也许本田很贵,你在测试中买不起。创建一个界面并在界面上运行GoToStore会使您与本田的依赖性脱钩。 GoToStore成为本田不可知论者。它可以在任何车辆上运行,甚至可以在任何车辆上运行。这里的依赖注入非常强大。也许是OOP中最强大的东西之一。它还允许您在测试套件中简单地存储内存汽车并对其进行断言。

func GoToStore(car Car) {
    car.IgniteEngine()
    car.Drive()
}

type StubCar struct {
  ignited bool
  driven bool
}
func (c *StubCar) IgniteEngine() { c.ignited = true }
func (c *StubCar) Drive() { c.driven = true}

func TestGoToStore(t *testing.T) {
  c := &StubCar{}
  GoToStore(c)
  assert.true(c.ignited)
  assert.true(c.driven)
}

同样的技巧可以应用于您的混凝土车类。假设你想要驾驶一辆本田车,而昂贵的部件则是发动机。通过让您的本田在发动机接口上运行,您可以在测试过程中切换非常昂贵的强大发动机,这些发动机只能用于生产,也可以用于weedwacker发动机。

依赖关系可以推向应用程序的边界,但在某些时候需要配置真正的引擎,真正的数据库驱动程序,真正昂贵的部分等,并将它们注入到生产应用程序中。 / p>

现在,如果Drive实际上依赖于外部库,如数据库或消息代理系统,该怎么办?

在某种程度上,必须测试这些集成。希望您的CI中的自动化方式不是片状,快速和可靠。但实际上可能是一次性手动的事情。 Depedency注入和接口允许您使用单元测试在内存中使用存根对象测试许多常见用例。即使使用接口和依赖注入,在某些时候,您仍然需要集成。像docker-compose这样的工具使得在CI管道中快速运行数据库和消息代理集成测试更加明智:)

这可以扩展到具体的本田级别。

type Honda struct {
   mysql MySQL
}
func (h *Honda) Drive() {
   h.mysql.UpdateState("driving")
}

此处可以应用相同的原则,以便您可以验证您的本田代码是否适用于存根数据存储。

type EngineMetrics interface {
    UpdateState(string)
}

type Honda struct {
    engineMetrics EngineMetrics
}
func (h *Honda) Drive() {
   h.engineMetrics.UpdateState("driving")
}

通过使用依赖注入和接口,本田与具体的度量数据库实现分离,允许使用测试存根来验证它是否有效。

答案 1 :(得分:2)

我认为问题不在界面本身中,而是Car的实施方式。好的可测试代码有利于组合,所以如果你有类似的东西:

type Engine interface {
  Ignite()
}

type Clutch interface {
  SwitchGear()
}

然后你可以买一辆这样的车:

type Car struct {
  engine Engine
  clutch Clutch
}

func (c *Car) IgniteEngine() {
  c.engine.Ignite()
}

...

通过这种方式,您可以替换Car中的引擎和离合器,并创建模拟离合器和引擎,从而产生您测试Drive方法所需的行为。