Go语言公有构造函数返回私有类型场景下表驱动测试实现方案咨询
Go非导出类型表驱动测试可行方案
以下是针对该场景的4种可落地方案,按推荐优先级排序:
方案1:定义公开接口替代具体类型(最高优先级)
无需修改原有app结构体的非导出属性,只需要在库代码中新增一个公开接口,声明app对外暴露的所有方法即可:
// mylib 包代码新增内容 type App interface { // 此处列出app对外暴露的所有公开方法,示例: Run() error SetConfig(cfg Config) error } // 编译期校验app实现了App接口,避免方法声明不匹配 var _ App = (*app)(nil)
测试用例中直接用接口类型声明字段即可,完全不破坏原有API暴露面:
// 测试用例结构体定义 tests := []struct { name string app mylib.App // 编译正常 }{ // 测试用例可直接传入mylib.New()生成的实例 {name: "测试用例1", app: mylib.New()}, }
方案2:测试用例存储构造逻辑,子测试内实例化
也就是你提到的高阶函数思路,零修改原库代码,完全不改动API设计:
如果测试用例只需要不同的构造参数,直接在测试结构体中存储构造参数,子测试内调用New生成实例即可:
func TestApp(t *testing.T) { tests := []struct { name string conf mylib.Conf // 存储New函数需要的入参 wantErr bool }{ {name: "合法配置", conf: mylib.Conf{Port: 8080}, wantErr: false}, {name: "非法端口", conf: mylib.Conf{Port: 0}, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 子测试内完成实例化 app := mylib.New(tt.conf) // 后续测试逻辑直接操作app即可 }) } }
如果需要提前定制不同的实例,也可以把字段替换为实例生成函数:
tests := []struct { name string appGen func() mylib.App // 存储实例生成函数 }{ { name: "预配置实例", appGen: func() mylib.App { app := mylib.New() app.SetConfig(mylib.Conf{Debug: true}) return app }, }, } // 子测试内调用生成函数即可:app := tt.appGen()
方案3:改为公有App类型,所有字段保持非导出
如果不想额外定义接口,这个方案改造成本极低,几乎没有副作用:
你只需要把app改为大写开头的App,所有字段保持小写非导出,外部仍然无法访问内部状态,只能调用你公开的方法,和原非导出结构体的封装效果完全一致,唯一变化是支持外部声明*mylib.App类型的变量:
// mylib包修改后代码 package mylib // App 对外暴露的应用实例,所有字段均为非导出,外部无法修改内部状态 type App struct { cfg Config // 保持小写非导出 srv *http.Server // 保持小写非导出 } func New() *App { return &App{} }
修改后测试代码可以直接声明app *mylib.App类型的字段,不需要额外调整测试逻辑。
方案4:改为同包白盒测试
如果你的测试不需要模拟用户视角的黑盒验证,纯粹测试内部逻辑,可以直接把测试文件的包声明从package mylib_test改为package mylib,即可直接访问非导出的app类型。但该方案属于白盒测试,不适合做对外API的行为验证。
不推荐方案
不建议用any/interface{}类型存储实例后断言,会丢失编译期类型检查,容易引入隐性测试错误。
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

