Golang测试中如何模拟结构体方法的调用

Golang测试中如何模拟结构体方法的调用 以下是结构体及其方法的示例代码

type A struct {}

func (a *A) perfom(string){
...
...
..
} 

然后我想从包外的函数 invoke() 调用该方法,示例代码:

var s := A{}
func invoke(url string){
out := s.perfom(url)
   ...
   ...
} 

现在,我想通过模拟 A 的 perform 方法来为 invoke 函数编写测试用例。 在 Java 中,我们有 mockito、jmock 框架来模拟方法调用。 在 Go 中是否有任何方法可以在不引入 接口 的情况下模拟结构体的方法调用?或者 Go 中是否有好的模拟框架?


更多关于Golang测试中如何模拟结构体方法的调用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

不客气! 😊

更多关于Golang测试中如何模拟结构体方法的调用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


非常感谢 😄

我尝试了两种解决方案,在测试函数中通过传递 mockA{} 来从测试方法调用 invoke,但出现了编译错误:无法在 invoke 参数中使用 m(类型为 mockA)作为类型 A

我看到以下快速选项:

  1. func invoke(url string) 改为:
func invoke(url string, f func(string))

并这样调用:

invoke(url, s.perform)

在你的测试中,编写一个模拟函数 func mockPerform(s string) 并将其传递给 invoke:

invoke(url, mockPerform)

编辑: 第二个选项是完全错误的。请参阅评论 #6

  1. 摆脱全局变量 s。(这是一条通用建议 - 避免使用全局变量。)invoke 改为包含结构体作为参数:
func invoke(url string, s A) {
    ...
}
...
s := A{}
invoke(url, s)

然后编写一个带有模拟函数的模拟结构体并将其传递给 invoke

type mockA struct{}
func (m *mockA) perform(s string) {
    ...
}

// 在你的测试函数中:

m := mockA{}
invoke(url, m)

这两种方法都不需要接口和不需要模拟框架。"秘诀"在于避免对你想要测试的函数外部的实体进行硬编码依赖。而是将它们作为参数传递。

你说得对,是我的错。我写这段代码太快了没有测试。对于第二个选项,你需要一个接口。让我从头开始。

你的库包不需要改变。不过有一个改动:将方法 Perform 的首字母改为大写 P,使其在包外可见。 为了测试目的,我还添加了一个返回参数。

perf/perf.go

package perf

type A struct{}

func (a *A) Perform(s string) int {
	return len(s)
}

在主程序中,你需要定义一个接口:

main.go

package main

import (
	"fmt"
	"perf" // 根据你放置perf的位置调整此路径
)

type Performer interface {
	Perform(string) int
}

func invoke(url string, p Performer) int {
	return p.Perform(url)
}

func main() {
	a := perf.A{}
	url := "https://appliedgo.net" // 无耻的宣传 :-)
	fmt.Println(invoke(url, &a))  // 这里需要传递指针,因为Perform()有一个指针接收器
}

在你的测试文件中,创建并使用 mockA。Invoke() 会接受它,因为它实现了 Performer 接口:

invoke_test.go:

package main

import "testing"

type mockA struct{}

func (m *mockA) Perform(s string) int {
	return len(s)
}

func Test_invoke(t *testing.T) {
	m := mockA{}
	url := "https://appliedgo.net"
	want := 21
	if got := invoke(url, &m); got != want {
		t.Errorf("invoke() = %v, want %v", got, want)
	}
}

(这次我测试了代码 🙂)

在Go语言中,如果不引入接口,模拟结构体方法调用相对困难,因为Go是一种静态类型语言,缺乏Java等语言中的动态代理机制。不过,有几种方法可以实现类似模拟:

  1. 使用条件编译:通过构建标签在测试时替换实现
  2. 使用函数变量:将方法替换为可赋值的函数变量
  3. 使用gomock或其他模拟框架(但通常需要接口)

以下是具体实现示例:

方法1:使用条件编译

原始代码:

// a.go
package main

type A struct{}

func (a *A) perform(url string) string {
    // 实际实现
    return "real result"
}

var s = &A{}

func invoke(url string) string {
    out := s.perform(url)
    return out
}

测试专用文件:

// a_test.go
package main

import "testing"

func TestInvoke(t *testing.T) {
    // 临时替换方法
    original := s.perform
    s.perform = func(url string) string {
        return "mocked result"
    }
    defer func() { s.perform = original }()

    result := invoke("test-url")
    if result != "mocked result" {
        t.Errorf("Expected 'mocked result', got '%s'", result)
    }
}

方法2:重构为使用函数变量

修改后的代码:

// a.go
package main

type A struct {
    performFunc func(string) string
}

func (a *A) perform(url string) string {
    if a.performFunc != nil {
        return a.performFunc(url)
    }
    // 默认实现
    return "real result"
}

var s = &A{}

func invoke(url string) string {
    out := s.perform(url)
    return out
}

测试代码:

// a_test.go
package main

import "testing"

func TestInvoke(t *testing.T) {
    // 设置模拟函数
    s.performFunc = func(url string) string {
        return "mocked result"
    }

    result := invoke("test-url")
    if result != "mocked result" {
        t.Errorf("Expected 'mocked result', got '%s'", result)
    }
}

方法3:使用测试框架(推荐)

虽然你提到不想引入接口,但使用接口是Go中更地道的做法。使用gomock框架:

// 定义接口
type Performer interface {
    perform(url string) string
}

var s Performer = &A{}

func invoke(url string) string {
    out := s.perform(url)
    return out
}

然后使用gomock生成模拟代码并进行测试。

总结: 虽然可以在不引入接口的情况下模拟方法调用,但这些方法都有局限性且不够优雅。在Go中,使用接口配合模拟框架(如gomock、testify/mock)是更推荐的做法,因为它提供了更好的类型安全和可维护性。

回到顶部