我对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?
答案 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/mockcompose,mockcompose
还可以帮助您测试常规函数,并调用从其他包中导入的函数。
答案 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