在Go中进行错误类型检查以进行单元表测试

I want to test the type of the error returned against a table test of expected results, like so:

var tabletest = []struct{
  instruction string
  want string
  err error
}{
  {"synonym for hi", "hello", nil}, // input, retval, errtype
  {"synonym for hig", "", TranslationError{}}, 
  {"sssnymm for hi", "", InstructionError{}},
}

func TestThesaurus(t *Testing) {
  for _, testcase := range tabletest {
    got, err := Thesaurus(testcase.instruction)
    // check error type
    // check result type
  }
}

In the example above, different error sub-classes are returned based on the type of error that occurred. You may imagine that the caller of the made-up Thesaurus function would handle each error type differently.

What is the idiomatic way to assert that the type of error returned, and the type of error expected, are the same?

reflect.TypeOf does the job:

import "reflect"

...

func TestThesaurus(t *Testing) {
  for _, testcase := range tabletest {
    got, err := Thesaurus(testcase.instruction)
    // check error type
    if goterr, wanterr := reflect.TypeOf(err), reflect.TypeOf(testcase.err); 
         goterr != wanterr {
      t.Errorf("For instruction %q, unexpected error: %q. Wanted %q", 
               testcase.instruction, goterr, wanterr)
    }
    // check result type
    if want := testcase.want; got != want {
      t.Errorf("For instruction %q, got %q, want %q.", 
               testcase.instruction, got, want)
    }
  }
}

Whether or not it's idiomatic is for the community to decide.

Use a type switch.

func TestThesaurus(t *Testing) {
  for _, testcase := range tabletest {
    got, err := Thesaurus(testcase.instruction)

    // Don't use && because we want to trap all cases where err is nil
    if err == nil  {
      if testcase.err != nil {
          // failure
      }
      continue
    }

    switch err.(type) {
    case TranslationError:
        if _,ok := (testcase.err).(TranslationError); !ok {
           // failure
        }
    case InstructionError:
        if _,ok := (testcase.err).(InstructionError); !ok {
           // failure
        }
    default:
        // Unrecognized error, failure
    }
}

It's definitely not as succinct as the reflect way of doing it, but I think it's more Go-ish and explicit.

There's also this idiom:

In Thesaurus...

import "errors"

var (
    TranslationError = errors.New("")
    InstructionError = errors.New("")
)

In Testcase...

if err != testcase.err {

}

However, I think in this idiom the errors must be defined in advance (i.e. the message cannot be changed).