测试Golang中的命名约定

web*_*rc2 14 go

我是第一次尝试单独测试Go包,我在同一个文件中有几个错误.

type FooErr int
type BarErr int

func (e *FooErr) Error () string {
    return "A Foo Error has occurred"
}

func (e *BarErr) Error () string {
    return "A Bar Error has occurred"
}
Run Code Online (Sandbox Code Playgroud)

但是,所有命名约定看起来都像这样func TestXxx(*testing.T)(来自testing包文档).这意味着我的测试文件看起来像这样:

func TestError (t *testing.T) { ... } // FooErr
func TestError (t *testing.T) { ... } // BarErr
Run Code Online (Sandbox Code Playgroud)

这显然是同一签名的两个功能.处理此问题的推荐方法是什么?

Kyl*_*ons 32

这里有几点需要考虑:

错误

包级别导出的错误值通常以后Err跟某些内容命名,例如ErrTimeout 此处.这样做是为了让你的包的客户可以做类似的事情

if err := yourpkg.Function(); err == yourpkg.ErrTimeout {
  // timeout
} else if err != nil {
  // some other error
}
Run Code Online (Sandbox Code Playgroud)

为方便起见,通常使用以下方法创建它们errors.New:

// Error constants
var (
  ErrTimeout = errors.New("yourpkg: connect timeout")
  ErrInvalid = errors.New("yourpkg: invalid configuration")
)
Run Code Online (Sandbox Code Playgroud)

或者使用自定义的,未导出的类型:

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] }
Run Code Online (Sandbox Code Playgroud)

后一种方法的一个优点是它不能与任何其他包的类型相比较.

如果您在错误中需要一些额外的数据,则类型的名称以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)
}
Run Code Online (Sandbox Code Playgroud)

与之前的相等检查相反,它需要一个类型断言:

tree, err := yourpkg.Parse(file)
if serr, ok := err.(*SyntaxError); ok {
  // syntax error
} else if err != nil {
  // other error
}
Run Code Online (Sandbox Code Playgroud)

在任何一种情况下,记录代码都很重要,这样包的用户就可以了解它们何时被使用以及哪些函数可能会返回它们.

测试

测试通常以他们正在测试的单元命名.在许多情况下,您不会单独测试错误条件,因此TestError不是经常出现的名称.但是,测试本身的名称必须是唯一的,并且不受约束以与示例相同的方式匹配被测代码中的任何内容.当您测试一段代码的多个条件时,通常最好将测试表示为表驱动测试.该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
  }
}
Run Code Online (Sandbox Code Playgroud)

如果你确实需要一个特殊的测试功能来完成一个奇怪且不适合主测试的单元,你通常会将它命名为TestParseTimeout包含单元和你正在测试的行为的描述.


Son*_*nia 6

我将按照惯例,在测试包的概述部分中记录的示例函数:

"为类型T声明函数F,类型T和方法M的示例的命名约定是:"

func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }
Run Code Online (Sandbox Code Playgroud)

godoc需要示例函数的命名约定,但为了一致性,我将遵循相同的测试惯例TestT_M.


And*_*nan 5

您不需要将Xxx部分TestXxx与实际函数名称匹配.为测试添加前缀的约定Test足以让go test命令获取它们.

就像Alex Lockwood在评论中所说,如果你愿意,可以使用TestFooError和TestBarError.

  • `Test` 单独有效,`TestXxx` 有效,但请注意 `Testxxx` 无效。 (3认同)