Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test Naming Conventions in Golang

Tags:

go

I'm trying to unit test a Go package for the first time, and I have a couple of errors in the same file.

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"
}

However, all naming conventions seem to look like this func TestXxx(*testing.T) (from the testing package documentation). This would mean my testing file would look like this:

func TestError (t *testing.T) { ... } // FooErr
func TestError (t *testing.T) { ... } // BarErr

Which is obviously two functions of the same signature. What is the recommended method for handling this?

like image 992
weberc2 Avatar asked Mar 01 '13 00:03

weberc2


2 Answers

There are a few things to consider here:

Errors

Package-level exported error values are typically named Err followed by something, for instance ErrTimeout here. This is done so that clients of your package can do something like

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

To facilitate this, they are often created either with errors.New:

// Error constants
var (
  ErrTimeout = errors.New("yourpkg: connect timeout")
  ErrInvalid = errors.New("yourpkg: invalid configuration")
)

or with a custom, unexported type:

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] }

One advantage of the latter approach is that it cannot compare equal with a type from any other package.

In the case where you need some extra data inside the error, the name of the type ends in 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)
}

which, in contrast to the previous equality check, requires a type assertion:

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

In either case, it is important to document your code so that users of your package understand when they will be used and what functions might return them.

Tests

Tests are often named after the unit that they're testing. In many cases, you won't test error conditions separately, so TestError is not a name that should come up very often. The name of the test itself merely has to be unique, however, and is not constrained to match anything in the code under test in the same way that examples are. When you're testing multiple conditions of a piece of code, it is often best to formulate the test as a Table Driven Test. That wiki page has some good examples, but to demonstrate error checking, you might do this:

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
  }
}

If you do need a special test function for a unit that does something weird and doesn't fit in the main test, you'd typically name it something descriptive like TestParseTimeout that includes both the unit and the behavior you're testing.

like image 183
Kyle Lemons Avatar answered Sep 29 '22 20:09

Kyle Lemons


I would follow the convention for example functions documented in the overview section of the testing package:

"The naming convention to declare examples for a function F, a type T and method M on type T are:"

func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

The naming convention for example functions is needed for godoc, but I'd follow the same convention for tests, TestT_M, for consistency.

like image 10
Sonia Avatar answered Sep 29 '22 20:09

Sonia