这里有几点需要考虑:
错误
包级导出的错误值通常命名Err
后跟一些东西,例如ErrTimeout
here。这样做是为了让您的包裹的客户可以执行类似的操作
if err := yourpkg.Function(); err == yourpkg.ErrTimeout {
// timeout
} else if err != nil {
// some other error
}
为了促进这一点,它们通常使用以下方式创建errors.New
:
// Error constants
var (
ErrTimeout = errors.New("yourpkg: connect timeout")
ErrInvalid = errors.New("yourpkg: invalid configuration")
)
或使用自定义的未导出类型:
type yourpkgError int
// Error constants
var (
ErrTimeout yourpkgError = iota
ErrSyntax
ErrConfig
ErrInvalid
)
var errText = map[yourpkgError]string{
ErrTimeout: "yourpkg: connect timed out",
...
}
func (e yourpkgError) Error() string { return errText[e] }
后一种方法的一个优点是它不能与任何其他包中的类型进行比较。
如果您在错误中需要一些额外数据,则类型的名称以 结尾Error
:
type SyntaxError struct {
File string
Line, Position int
Description string
}
func (e *SyntaxError) Error() string {
return fmt.Sprintf("%s:%d:%d: %s", e.File, e.Line, e.Position, e.Description)
}
与之前的相等性检查相比,它需要一个类型断言:
tree, err := yourpkg.Parse(file)
if serr, ok := err.(*SyntaxError); ok {
// syntax error
} else if err != nil {
// other error
}
在任何一种情况下,记录您的代码都很重要,以便您的包的用户了解何时使用它们以及哪些函数可能会返回它们。
测试
测试通常以他们正在测试的单元命名。在许多情况下,您不会单独测试错误条件,因此TestError
这个名称不应该经常出现。然而,测试本身的名称必须是唯一的,并且不限于以与示例相同的方式匹配被测代码中的任何内容。当您测试一段代码的多个条件时,通常最好将测试制定为Table Driven Test。该 wiki 页面有一些很好的示例,但为了演示错误检查,您可以这样做:
func TestParse(t *testing.T) {
tests := []struct{
contents string
err error
}{
{"1st", nil},
{"2nd", nil},
{"third", nil},
{"blah", ErrBadOrdinal},
{"", ErrUnexpectedEOF},
}
for _, test := range tests {
file := strings.NewReader(test.contents)
if err := Parse(file); err != test.err {
t.Errorf("Parse(%q) error %q, want error %q", test.contents, err, test.err)
}
// other stuff
}
}
如果你确实需要一个特殊的测试函数来为一个单元做一些奇怪的事情并且不适合主测试,你通常会把它命名为一个描述性的东西TestParseTimeout
,包括你正在测试的单元和行为。