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
非常感谢 😄
我尝试了两种解决方案,在测试函数中通过传递 mockA{} 来从测试方法调用 invoke,但出现了编译错误:无法在 invoke 参数中使用 m(类型为 mockA)作为类型 A
我看到以下快速选项:
- 将
func invoke(url string)改为:
func invoke(url string, f func(string))
并这样调用:
invoke(url, s.perform)
在你的测试中,编写一个模拟函数 func mockPerform(s string) 并将其传递给 invoke:
invoke(url, mockPerform)
编辑: 第二个选项是完全错误的。请参阅评论 #6。
- 摆脱全局变量
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等语言中的动态代理机制。不过,有几种方法可以实现类似模拟:
- 使用条件编译:通过构建标签在测试时替换实现
- 使用函数变量:将方法替换为可赋值的函数变量
- 使用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)是更推荐的做法,因为它提供了更好的类型安全和可维护性。

