如何在Golang中仅模拟特定方法

时间:2017-03-16 16:19:48

标签: unit-testing go interface override

我对golang很新,我正在努力完成一项简单的任务。

我在golang中有以下课程

type struct A {
}

func (s *A) GetFirst() {
    s.getSecond()
}

func (s *A) getSecond() {
    // do something
}

我想为它编写一些测试,但是我需要覆盖getSecond()。我试图在我的测试文件中执行以下操作

type Ai interface {
    getSecond()
}

type testA struct {
    A
}

func (s *testA) getSecond() {
     // do nothing
}

func TestA(t *testing.T) {
   a := &A{}
   t := &testA{A: a}
   t.GetFirst()  
}

这里的想法是将A getSecond()方法暴露给接口并使用嵌入覆盖但是这似乎不起作用。该测试仍然调用getSecond()的原始实现,而不是我的模拟实现。

一个解决方案当然是为A创建一个包含getFirst()getSecond()的正确接口,然后在测试中创建一个结构,实现两个getFirst()调用原始实现的结构和getSecond()一个假人,但我觉得这很麻烦,而不是正确的做事方式。

另一种可能性是将实际实现中的getSecond()分配给变量并覆盖测试中的变量,但我也觉得这样做只是为了简单的覆盖有点奇怪。

我对此错了吗?有没有更简单的方法来使用golang?

3 个答案:

答案 0 :(得分:3)

根据this answer,您无法真正覆盖golang中的方法。但是,正如您所指出的那样,您可以为" getSecond方法"提供单独的界面。并且在您的测试用例中有一个实现,在实际代码中有一个实现。

type s interface{ 
    getSecond()
}

type A struct{
    s
}

type a struct{

}

func (s *A) GetFirst() {
    s.getSecond()
}

func (s a) getSecond() {
    // do something
}

//Use a 
A{a{}}

然后在Test中有一个不同的实现' a'

type struct ta {

}
func (s ta) getSecond() {
    // do nothing
}

A{ta{}}

答案 1 :(得分:1)

mockcompose (https://github.com/kelveny/mockcompose) 是我为了解决这个问题而创建的。

Go 中的 Class 不是一等公民,对于那些来自其他语言世界(即 Java)的人来说,缺乏模拟兄弟方法的能力有时会令人烦恼。

假设你有一个类 foo

package foo

type foo struct {
    name string
}

func (f *foo) Foo() string {
    if f.Bar() {
        return "Overriden with Bar"
    }

    return f.name
}

func (f *foo) Bar() bool {
    if f.name == "bar" {
        return true
    }

    return false
}

您想测试 Foo() 方法,但是,当 Foo() 调用 Bar() 时,您希望 Bar() 被模拟。

使用 mockcompose,您可以先配置 go generate,然后让 mockcompose 为您生成管道设备。

mocks.go

//go:generate mockcompose -n testFoo -c foo -real Foo -mock Bar
package foo

mockcompose 然后会生成代码:mockc_testFoo_test.go

//
// CODE GENERATED AUTOMATICALLY WITH github.com/kelveny/mockcompose
// THIS FILE SHOULD NOT BE EDITED BY HAND
//
package foo

import (
    "github.com/stretchr/testify/mock"
)

type testFoo struct {
    foo
    mock.Mock
}

func (f *testFoo) Foo() string {
    if f.Bar() {
        return "Overriden with Bar"
    }
    return f.name
}

func (m *testFoo) Bar() bool {

    _mc_ret := m.Called()

    var _r0 bool

    if _rfn, ok := _mc_ret.Get(0).(func() bool); ok {
        _r0 = _rfn()
    } else {
        if _mc_ret.Get(0) != nil {
            _r0 = _mc_ret.Get(0).(bool)
        }
    }

    return _r0

}

现在,您只需像在其他语言(即 Java)中一样编写单元测试逻辑,例如:

func TestFoo(t *testing.T) {
    assert := require.New(t)

    fooObj := &testFoo{}

    // Mock sibling method Bar()
    fooObj.On("Bar").Return(false)

    s := fooObj.Foo()
    assert.True(s == "")
}

有关详细信息,请查看 https://github.com/kelveny/mockcomposemockcompose 还可以帮助您测试常规函数,并调用从其他包中导入的函数。

答案 2 :(得分:0)

我认为至少有两种选择。

始终使用函数字段

当被模拟的函数与我正在模拟的结构无关时,可能是第三方函数,或者是从我的应用程序的另一个组件编写的东西。 我会在初始化服务时分配真正的工作或测试时的模拟。

// service.go
type MyService struct {
    getRandomID func() string
}

type Car struct {
    ID   string
    Name string
}

func (s *MyService) NewCar() (*Car, error) {
    car := Car{
        ID:   s.getRandomID(),
        Name: "ThisCar",
    }

    return &car, nil
}


// service_test.go

func newIDsForTests() func() string {
    i := 0
    return func() string {
        i++
        return fmt.Sprintf("%024d", i)
    }
}

func TestNewCar(t *testing.T) {
    s := MyService{
        getRandomID: newIDsForTests(),
    }

    actual, err := s.NewCar()

    if err != nil {
        panic(err)
    }

    expected := Car{ID: "000000000000000000000001", Name: "ThisCar"}
    if *actual != expected {
        panic("cars don't match")
    }
}


The Go Playground working example

仅在模拟时使用函数字段

当要模拟的函数与我正在使用的结构真正相关时,它就是该组件的一部分。 我将始终使用实际工作,并在测试需要时分配模拟功能。

虽然我认为这个解决方案相当丑陋,但我也认为它确实易于使用和维护,同时还可以让您对代码进行 100% 的单元测试!

我的想法是在结构中添加一个字段 mockedGetSecond,并且仅在要模拟真实 getSecond 的测试中设置其值。在实际实现中,您当然必须添加检查,如果此 func 不是 nil,则必须使用它。

这可能不是一个好的模式,或者我想经常使用的东西,但我想我会用它来模拟一个做很多逻辑的函数(和很多数据库调用,需要各种input, ...) 并且经常在同一服务的函数中调用。

// service.go

import (
    "fmt"
    "testing"
)

type MyService struct {
    mockedGetSecond func() (string, error)
}

func (s *MyService) GetFirst() error {
    secondVal, err := s.getSecond()
    if err != nil {
        return err
    }

    fmt.Println("getSecond returned: ", secondVal)

    return nil
}

func (s *MyService) getSecond() (string, error) {
    if s.mockedGetSecond != nil {
        return s.mockedGetSecond()
    }

    // very complex function

    return "real", nil
}

// service_test.go

func TestGetFirst(t *testing.T) {
    myService := MyService{
        mockedGetSecond: func() (string, error) {
            return "mocked", nil
        },
    }

    err := myService.GetFirst()
    if err != nil {
        panic(err)
    }
}

func TestGetSecond(t *testing.T) {
    myService := MyService{}

    actual, err := myService.getSecond()
    if err != nil {
        panic(err)
    }

    if actual != "real" {
        panic("I would expect 'real'")
    }
}

The Go Playground working example

=== RUN   TestGetFirst
getSecond returned:  mocked
--- PASS: TestGetFirst (0.00s)
=== RUN   TestGetSecond
--- PASS: TestGetSecond (0.00s)
PASS