测试必须设 gin.TestMode,否则日志和 panic 行为异常;配置需用 viper.New() 隔离实例、os.Setenv 配合 defer os.Unsetenv 清理,并避免 init() 中加载配置。

测试时必须设 gin.TestMode,否则日志和 panic 捕获行为不一致
不设这个模式,gin.Default() 会启用 Logger 和 Recovery 中间件,导致测试输出混杂、panic 不被捕获,断言失败时难以定位。正确做法是在测试入口统一设置:
-
func TestMain(m *testing.M) { gin.SetMode(gin.TestMode); m.Run() }—— 放在main_test.go里,对整个测试包生效 - 别在每个
TestXxx函数里重复调gin.SetMode,它不是线程安全的,多 goroutine 并发调用可能覆盖彼此 - 如果用了
viper加载配置,TestMode不影响其行为,但你要确保配置加载逻辑没硬编码环境名(比如写死"prod")
配置隔离靠 os.Setenv + viper.Reset(),不能依赖全局 viper 实例
多个测试用例共用一个 viper.Viper 实例会导致配置污染——前一个测试改了 ENV=dev,后一个测试没清理就跑 ENV=prod,结果读错配置。实操要点:
- 每个测试函数开头加
os.Setenv("ENV", "test"),结尾加defer os.Unsetenv("ENV") - 调用
viper.New()创建全新实例,而不是复用global.Viper—— 否则viper.WatchConfig()可能在测试中意外触发重载 -
viper.AddConfigPath("./config/test")指向专用测试配置目录,避免和开发/生产配置文件冲突 - 显式调
viper.ReadInConfig()并检查 error;若失败,用t.Fatal()终止当前测试,别让后续断言在空配置下瞎跑
测试中读取配置要绑定结构体,别用 viper.GetString 拼接
直接调 viper.GetString("server.port") 看似简单,但字段名拼错、类型不匹配、缺默认值都会导致运行时 panic 或返回空字符串,且测试无法提前暴露问题。正确方式是:
- 定义结构体带
mapstructuretag:type Config struct { Server struct { Port int `mapstructure:"port" default:"8080"` } `mapstructure:"server"` } - 用
viper.Unmarshal(&cfg)一次性绑定,字段缺失时按defaulttag 填充,类型错误会直接报错 - 测试里可故意删掉
config.test.yaml中的server.port字段,验证是否真走 default;若仍 panic,说明没加 tag 或没调Unmarshal
环境变量和配置文件路径别写死在 init() 里
把 os.Getenv("ENV") 或 viper.AddConfigPath() 放进 init() 函数,会导致单元测试无法控制环境上下文——测试还没开始,配置已经加载完毕,os.Setenv 失效。关键约束:
- 所有配置加载逻辑必须放在
main()或测试函数内部,绝不在init() - 若用
flag.String("env", "dev", "env name"),测试时用flag.Set("env", "test")替代环境变量,更可控 - 测试配置文件路径建议用相对路径
"./config/test/config.yaml",避免os.Getwd()在不同执行目录下返回不同结果
测试配置隔离最难绷的一点:不是“怎么设”,而是“什么时候清”。os.Setenv 的作用域是进程级,哪怕你 defer os.Unsetenv,一旦某个测试 panic 退出,清理代码就不执行,后续测试全被污染。所以优先用 viper.New() 隔离实例,环境变量只是辅助。











