読者です 読者をやめる 読者になる 読者になる

テストデータに期待される値を埋め込む

エラー処理のテストを書いているのですが、期待されるエラーメッセージをテストデータの中に埋め込むとテストコードを変更することなしにどんどんテストケースを追加することができます。
たとえば、

[[ページ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で使われているもので、なかなか便利じゃないかと思って使っています。