Go使用assert.Equal比对含time.Time字段的interface对象测试失败如何解决
问题描述
我想要校验返回数据是否符合预期。
我的函数代码如下:
func extractData(payload string) (interface{}, time.Time, error) { eventTime := gjson.Get(payload, "data.eventDateTime").String() dateTime, err := time.Parse("2006-01-02T15:04:05-07:00", eventTime) if err != nil { return nil, time.Time{}, fmt.Errorf("Date Time Error: %w", err) } body := data.Body{ EventTime: dateTime, } return body, dateTime, nil }
我为其编写的单元测试代码如下:
func TestExtractData(t *testing.T) { tests := []struct { Name string Payload string ExpectedOutput interface{} }{ { Name: "test-2", Payload: `{"data":"2020-11-02T10:44:48+01:00"}`, ExpectedOutput: data.Body{ EventTime: time.Date(2020, 11, 2, 10, 44, 48, 0, time.FixedZone("CET", 3600)), }, }, } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { data, _, _ := extractData(tt.Payload) assert.Equal(t, tt.ExpectedOutput, data) }) } }
测试运行失败,输出如下:
{ 2020-11-02 10:44:48 +0100 CET} does not equal { 2020-11-02 10:44:48 +0100 CET}
我怀疑问题出在时区上,但不知道如何编写测试代码。我不想单独使用time.Equal做校验,因为我的data.Body并不总是包含eventTime字段。
我临时强转为具体类型使用如下代码测试,可以正常通过:
if !tt.ExpectedOutput.EventTime.Equal(data.EventTime) { //throw error }
我还尝试过如下写法,但是测试失败:
if !reflect.DeepEqual(tt.ExpectedOutput.EventTime, data.EventTime) { t.Errorf("extractData() output = %v, want = %v",data,tt.ExpectedOutput) }
请问该如何解决这个问题?
解决方案
根本原因
Go标准库的reflect.DeepEqual比较time.Time类型时,除了比较时间点和时区偏移,还会比较内部*time.Location指针是否指向同一个实例。你测试用例中手动用time.FixedZone生成的时区实例,和time.Parse从字符串解析出来的时区实例是两个不同的内存对象,哪怕偏移和名称完全一致,也会被判为不等,testify/assert的Equal方法底层默认用reflect.DeepEqual,所以会出现打印内容一致但测试失败的情况。
可选方案
方案1:使用自定义比较器适配testify断言
如果已经在使用testify库,可以直接给assert.Equal传入time.Time类型的自定义比较规则,自动适配所有包含time.Time字段的结构体,不需要单独处理EventTime字段:
assert.Equal(t, tt.ExpectedOutput, data, assert.Comparer(func(a, b time.Time) bool { return a.Equal(b) }))
该比较器会自动识别所有time.Time类型的字段,用官方推荐的time.Equal判断相等,其他类型字段保持默认比较逻辑。
方案2:统一预期值的时间构造方式
构造测试预期值时,直接用和业务代码相同的time.Parse方法生成EventTime,保证时区实例完全一致:
eventTime, _ := time.Parse("2006-01-02T15:04:05-07:00", "2020-11-02T10:44:48+01:00") ExpectedOutput: data.Body{ EventTime: eventTime, },
这种方式不需要修改原有断言逻辑,用默认的assert.Equal即可通过测试。
方案3:使用go-cmp库做结构化比较
如果可以引入新依赖,go-cmp库默认支持time.Time的等值判断,会自动调用time.Equal,不需要额外配置:
if diff := cmp.Diff(tt.ExpectedOutput, data); diff != "" { t.Errorf("结果不匹配 (-预期值 +实际值):\n%s", diff) }
该方式会输出详细的差异字段,适合复杂结构体的比较场景。
内容的提问来源于stack exchange,提问作者Jasiuoo

