Go语言结构体单元测试:完善VSCode生成的YAML解析测试用例
完善Go YAML解析与属性校验测试的方案
你已经搭好了基础的测试架子,但要全面覆盖YAML解析和属性校验的所有场景,还得从测试用例维度、错误验证逻辑、代码拆分这几个方面入手。下面是具体的完善步骤:
1. 补充覆盖全场景的测试用例
当前的测试只覆盖了“合法YAML解析成功”这一种情况,要覆盖所有逻辑,得补充以下几类用例:
- 正常合法场景(不同结构的有效YAML)
- YAML格式错误场景(语法、缩进问题)
- 属性校验失败场景(必填字段缺失、类型不匹配、枚举值非法等)
- 边界场景(空文件、超大字段、特殊字符输入)
修改后的测试用例结构体可以这样写:
type args struct { ztrFile string } // 扩展测试用例,覆盖全场景 tests := []struct { name string args args wantOut ZTR wantErr bool // 标记是否预期执行报错 errContains string // 预期错误信息中必须包含的关键词 }{ // 正常场景:完整合法的YAML配置 { name: "valid_full_config", args: args{ztrFile: "./testdata/valid_full.yaml"}, wantOut: expectedFullZTR, // 提前构造好预期的ZTR实例 wantErr: false, }, // YAML格式错误:冒号后未加空格,触发语法解析失败 { name: "invalid_yaml_syntax", args: args{ztrFile: "./testdata/invalid_syntax.yaml"}, wantErr: true, errContains: "yaml: syntax error", }, // 属性校验失败:必填字段modules缺失 { name: "missing_required_modules", args: args{ztrFile: "./testdata/missing_modules.yaml"}, wantErr: true, errContains: "required field 'modules' is missing", }, // 属性校验失败:字段类型不匹配(比如把数字类型的端口写成字符串) { name: "wrong_field_type", args: args{ztrFile: "./testdata/wrong_type.yaml"}, wantErr: true, errContains: "cannot unmarshal string into Go struct field", }, // 边界场景:空文件 { name: "empty_yaml_file", args: args{ztrFile: "./testdata/empty.yaml"}, wantErr: true, errContains: "yaml: unmarshal errors", }, }
2. 完善测试逻辑,增加错误验证
原来的测试只处理了成功场景,现在要适配错误场景的验证。首先要确保你的parseFile函数能返回错误(如果原来没返回,得先修改它,让解析或校验失败时返回对应的error),然后修改测试循环:
import ( "reflect" "strings" "testing" ) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 假设parseFile现在返回(out ZTR, err error) gotOut, err := parseFile(tt.args.ztrFile) // 先验证错误是否符合预期 if (err != nil) != tt.wantErr { t.Errorf("parseFile() error = %v, wantErr %v", err, tt.wantErr) return } // 如果预期有错误,验证错误信息是否包含指定关键词 if tt.wantErr { if !strings.Contains(err.Error(), tt.errContains) { t.Errorf("parseFile() error message = %q, should contain %q", err.Error(), tt.errContains) } return // 错误场景无需验证返回值 } // 成功场景验证返回值是否与预期一致 if !reflect.DeepEqual(gotOut, tt.wantOut) { t.Errorf("parseFile() = %+v, want %+v", gotOut, tt.wantOut) } }) }
3. 额外优化建议
- 提前构造预期结构体:对于合法场景的
wantOut,可以在测试文件顶部提前构造好预期的ZTR实例,比如:var expectedFullZTR = ZTR{ Modules: []Module{ {Name: "auth-service", Version: "v2.1.0"}, {Name: "payment-service", Version: "v1.5.3"}, }, // 填充其他顶层字段 } - 拆分校验逻辑单独测试:如果属性校验逻辑复杂,建议把它从
parseFile中抽成独立函数(比如validateZTR(z ZTR) error),然后针对这个函数写专门的单元测试——直接构造各种合法/非法的ZTR结构体传入,不需要依赖YAML文件,测试效率更高:func TestValidateZTR(t *testing.T) { tests := []struct { name string inputZTR ZTR wantErr bool errContains string }{ { name: "valid_ztr_with_correct_versions", inputZTR: ZTR{ Modules: []Module{{Name: "user-service", Version: "v1.0.0"}}, }, wantErr: false, }, { name: "invalid_version_format", inputZTR: ZTR{ Modules: []Module{{Name: "order-service", Version: "invalid-version"}}, }, wantErr: true, errContains: "invalid version format: must follow semver rules", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateZTR(tt.inputZTR) if (err != nil) != tt.wantErr { t.Errorf("validateZTR() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && !strings.Contains(err.Error(), tt.errContains) { t.Errorf("validateZTR() error message = %q, should contain %q", err.Error(), tt.errContains) } }) } } - 保持testdata目录整洁:继续把所有测试用的YAML文件放在
testdata目录下,避免和业务代码混淆,也方便后续维护。
内容的提问来源于stack exchange,提问作者user9528667
相关产品推荐
相关产品推荐

