テストデータに期待される値を埋め込む
エラー処理のテストを書いているのですが、期待されるエラーメッセージをテストデータの中に埋め込むとテストコードを変更することなしにどんどんテストケースを追加することができます。
たとえば、
[[ページ1が開きます。 `` ERROR "1:2: expecting \"]]\""`` 普通''強調普通 `` ERROR "3:5: expecting \"''\""`` あああ[[ページ `` ERROR "5:6: expecting \"]]\""`` -[[ページ1が開きます。 `` ERROR "7:3: expecting \"]]\""``
といったようにコメント(``と``ではさまれた部分)にメッセージを書きます。ただし、普通のコメントもありうるので、`^ *ERROR *"(.*)" *`といったパターンにマッチさせます。
テストコードは以下のように、ErrorBuilderはVisitでerrorsにエラーになった場所のキー、コメントを値にいれます。それと実際にParserが集めたerrorsをcompareErrorsで比べています。
type ErrorBuilder struct { errors map[Position]string prev Position } func (b *ErrorBuilder) init() var errRx = regexp.MustCompile(`^ *ERROR *"(.*)" *`) func (b *ErrorBuilder) Visit(node Node, context int) func compareErrors(t *testing.T, path string, expected map[Position]string, found ErrorList) { if len(expected) != found.Len() { t.Errorf("expected:%d errors, but actual:%d errors", len(expected), len(found)) } for _, error := range found { pos := error.Pos if msg, found := expected[pos]; found { Msg := fmt.Sprintf("%q", error.Error()) Msg = Msg[1:len(Msg)-1] if msg != Msg { t.Errorf("%s: %q does not match %q", error.Pos.String(), Msg, msg) continue } delete(expected, pos) } else { t.Errorf("%s:%s: unexpected error: %q", path, error.Pos.String(), error.Msg) } } } var pathRx = regexp.MustCompile(`^[^\.].*?.txt`) func TestParseErrors(t *testing.T) { list, err := ioutil.ReadDir("./testdata") if err != nil { fmt.Fprintf(os.Stderr, "%v", err) os.Exit(1) } for _, finfo := range list { if m := pathRx.MatchString(finfo.Name()); !m || finfo.IsDir() { continue } path := fmt.Sprintf("%s/%s", "./testdata", finfo.Name()) f, err := os.Open(path) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } var ( p Parser b ErrorBuilder ) b.init() src, _ := ioutil.ReadAll(f) p.Init(src) b.Visit(p.Parse(), CONTEXT_NONE) if len(p.errors) == 0 { t.Errorf("expected errors but no errors\n") } compareErrors(t, path, b.errors, p.errors) } }
この方法は、go/src/pkg/go/parser/error_test.goで使われているもので、なかなか便利じゃないかと思って使っています。