脱敏测试必须覆盖多出口场景:http响应、日志(zap/zerolog)、配置加载(viper环境变量绕过)、ac自动机敏感词替换,且各机制需独立验证。

测试 json.Marshaler 脱敏输出必须用指针接收器
直接调用 json.Marshal(u) 时,如果 MarshalJSON 方法是值接收器(func (u User) MarshalJSON()),嵌套结构体或 nil 字段不会被正确处理,脱敏逻辑可能完全不触发。这是最常被忽略的测试失败原因。
实操建议:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 测试前确保方法签名是
func (u *User) MarshalJSON() ([]byte, error),哪怕结构体本身不嵌套 - 构造含 nil 指针的测试用例,例如
&User{Phone: nil},验证是否 panic 或返回空字段 - 用
reflect.DeepEqual比对期望 JSON 字节流,而不是字符串拼接——避免引号、空格、转义差异导致误判 - 别在测试里写
json.Unmarshal回填再比对字段值:脱敏只影响输出,原始字段值不应被修改
测试配置加载阶段脱敏要覆盖环境变量绕过路径
viper 的 DecodeHookFunc 在纯文件加载时生效,但一旦启用 AutomaticEnv(),像 VIPER_DATABASE_PASSWORD=123456 这类环境变量会跳过 hook 直接注入结构体,测试若只覆盖 YAML 文件就等于没测真实场景。
实操建议:
- 测试必须显式调用
viper.BindEnv("database.password", "DATABASE_PASSWORD"),再设os.Setenv("DATABASE_PASSWORD", "raw") - 验证脱敏后结构体字段是否为
"[REDACTED]",而非空字符串——否则json:",omitempty"会让字段消失 - 检查 key 路径提取逻辑:hook 函数里拿到的
keyPath应该是"database.password",不是仅"password";可用viper.AllKeys()辅助断言 - 测试非字符串字段(如
Timeout int)是否被 hook 错误拦截并 panic——hook 必须先判断f.Kind() == reflect.String && t.Kind() == reflect.String
测试日志脱敏得区分 zap 和 zerolog 的接口契约
zap 要求结构体实现 zapcore.ObjectMarshaler(即 MarshalLogObject),而 zerolog 没有等价接口,只能靠 InterfaceHook 拦截 map[string]interface{}。混用同一套测试逻辑会导致一个库通过、另一个库漏脱敏。
实操建议:
- 对 zap:传
zap.Object("user", &u),验证输出是否含"token":"[redacted]",而非原始值;注意字段必须导出(Token string),小写token string会被反射跳过 - 对 zerolog:用
log.Info().Interface("req", req).Send(),配合自定义SanitizeMaphook,测试需递归进入req.Header和req.URL.Query() - 禁止在
MarshalLogObject里解引用未判空的指针(如u.Profile.Token),测试应包含Profile: nil用例,确认不 panic - zerolog 的
InterfaceHook输入是interface{},测试时需传入 map 类型,其他类型(如int、time.Time)应原样返回,不能强转
测试 AC 自动机敏感词替换要防索引偏移和 Unicode 干扰
用 strings.ReplaceAll 批量替换会导致匹配位置漂移,比如“苹果手机”中“苹果”被替成 [屏蔽] 后,原长度 2 变成 4,后续“手机”的起始索引就错乱了。测试若只校验最终字符串是否含 [屏蔽],会漏掉这种偏移引发的漏匹配。
实操建议:
- 测试必须用
ac.FindAllStringIndex(text, true)获取所有重叠匹配区间,再按[i][0]倒序排序后逐个替换 - 构造含全角空格、零宽字符的测试文本,例如
"苹\u3000果"(全角空格)或"苹\u200b果"(零宽连接符),验证预处理是否归一化成功 - 词库含大小写混合时(如 "Apple" 和 "apple"),测试需覆盖输入为 "ApplePhone" 的场景,确认只命中 "Apple" 而非误切 "AppleP"
- 避免在测试中每次调用都
ahocorasick.NewAhoCorasick()—— 预编译一次词典,复用实例,否则 GC 压力会干扰性能断言
User 结构体,可能走 HTTP 响应、走 zap 日志、走配置加载、走后台任务消息,每个出口的脱敏机制完全不同,且相互不感知。漏掉任意一个,明文就从那个口子流出去了。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










