Go 区分了 “测试辅助函数 “和 “断言辅助函数”。
t.Helper
通常是合适的,将其标记为测试辅助函数。参见 测试辅助函数的错误处理 了解更多细节。测试的目的是报告被测试代码的通过/失败情况。测试失败的理想场所是在Test
函数本身,因为这样可以确保失败信息和测试逻辑是清晰的。
随着你的测试代码的增长,可能有必要将一些功能分解到独立的函数中。标准的软件工程考虑仍然适用,因为测试代码仍然是代码。如果这些功能不与测试框架交互,那么所有的常规规则都适用。然而,当通用代码与框架交互时,必须注意避免常见的陷阱,这些陷阱会导致语焉不详的失败信息和不可维护的测试。
如果许多独立的测试用例需要相同的验证逻辑,请以下列方式之一安排测试,而不是使用断言辅助函数或复杂的验证函数。
Test
函数中内联逻辑(包括验证和失败),即使它是重复的。这在简单的情况下效果最好。测试
中使用逻辑来决定是否失败,并提供有用的测试失败。你也可以创建测试辅助函数,以剔除常见的模板设置代码。最后一点中概述的设计保持了正交性。例如,cmp
包 不是为了测试失败而设计的,而是为了比较(和差异)值。因此,它不需要知道进行比较的上下文,因为调用者可以提供这个。如果你的普通测试代码为你的数据类型提供了一个cmp.Transformer
,这通常是最简单的设计。对于其他的验证,可以考虑返回一个error
值。
// Good:
// polygonCmp returns a cmp.Option that equates s2 geometry objects up to
// some small floating-point error.
func polygonCmp() cmp.Option {
return cmp.Options{
cmp.Transformer("polygon", func(p *s2.Polygon) []*s2.Loop { return p.Loops() }),
cmp.Transformer("loop", func(l *s2.Loop) []s2.Point { return l.Vertices() }),
cmpopts.EquateApprox(0.00000001, 0),
cmpopts.EquateEmpty(),
}
}
func TestFenceposts(t *testing.T) {
// This is a test for a fictional function, Fenceposts, which draws a fence
// around some Place object. The details are not important, except that
// the result is some object that has s2 geometry (github.com/golang/geo/s2)
got := Fencepost(tomsDiner, 1*meter)
if diff := cmp.Diff(want, got, polygonCmp()); diff != "" {
t.Errorf("Fencepost(tomsDiner, 1m) returned unexpected diff (-want+got):\n%v", diff)
}
}
func FuzzFencepost(f *testing.F) {
// Fuzz test (https://go.dev/doc/fuzz) for the same.
f.Add(tomsDiner, 1*meter)
f.Add(school, 3*meter)
f.Fuzz(func(t *testing.T, geo Place, padding Length) {
got := Fencepost(geo, padding)
// Simple reference implementation: not used in prod, but easy to
// reasonable and therefore useful to check against in random tests.
reference := slowFencepost(geo, padding)
// In the fuzz test, inputs and outputs can be large so don't
// bother with printing a diff. cmp.Equal is enough.
if !cmp.Equal(got, reference, polygonCmp()) {
t.Errorf("Fencepost returned wrong placement")
}
})
}
polygonCmp
函数对它的调用方式是不可知的;它不接受具体的输入类型,也不规定在两个对象不匹配的情况下该怎么做。因此,更多的调用者可以使用它。
注意:在测试辅助函数和普通库代码之间有一个类比。库中的代码通常应该不 panic,除非在极少数情况下;从测试中调用的代码不应该停止测试,除非继续进行没有意义。