Categories
Uncategorized

External Tests

I saw Eli Bendersky’s File-driven testing in Go post, and really like it. I was using something very similar yesterday. I’ve been attempting to replace a custom parser written in Python with an ANTLR one, with the goal being to run the same parser in both Python and Go. In order to do that, we need test cases that verify the old regex-based parser and the new antlr-based parser produce the same results. In order to do this, I moved all the test cases into a textproto (which is a common answer inside Google!). The output of the parser is another protobuf message, so we can include that directly.

test_case: {
  input: "some/valid/input"
  output: { … expected proto message inline … }
  want_error: false
}
test_case: { … }
// repeat as needed

The schema is pretty simple.

message TestCase {
  string input = 1;
  MyMessage output = 2;
  bool want_error = 3;
}

message TestCases {
  repeated TestCase test_case = 1;
}

This is trivially usable in the Go code, generating a subtest for each case.

func TestParse(t *testing.T) {
  data, err := os.Read("testdata/parser_test_cases.textproto")
  if err != nil { t.Fatal(err) }

  cases := &pb.TestCases{}
  if err := proto.UnmarshalText(data); err != nil { t.Fatal(err) }

  for _, tc := range cases {
    t.Run(tc.GetInput(), func(t *testing.T) {
      got, err := Parse(tc.input)
      if gotErr := (err != nil); gotErr != tc.getWantError() {
        t.Errorf("Parse() got err? %t want err? %t (err: %v)", gotErr. tc.GetWantError(), err)
      }
      if diffs := cmp.Diff(want, got, protocmp.Transform()) {
        t.Errorf("Parse() had unexpected differences (-want +got):\n%s", diffs)
      }
    })
  }
}

This makes it really easy to add new test cases, as I develop new parts of the ANTLR parser. The Python code is very similar to the Go code (both in the ANTLR and regex case). I was pleased to discover that Python has subtests along the way!