My favorites | Sign in
Project Home Wiki
Search
for
TableDrivenTests  
Writing test cases
testing, table-driventests
Updated Apr 18, 2012 by dave.and...@gmail.com

Introduction

Writing good tests is not trivial, but in many situations a lot of ground can be covered with table-driven tests: Each table entry is a complete test case with inputs and expected results, and sometimes with additional information such as a test name to make the test output easily readable. If you ever find yourself using copy and paste when writing a test, think about whether refactoring into a table-driven test or pulling the copied code out into a helper function might be a better option.

Given a table of test cases, the actual test simply iterates through all table entries and for each entry performs the necessary tests. The test code is written once and amortized over all table entries, so it makes sense to write a careful test with good error messages.

Example of a table driven test

Here is a good example from the testing code for the fmt package ( http://golang.org/pkg/fmt/ ):

var flagtests = []struct {
	in  string
	out string
}{
	{"%a", "[%a]"},
	{"%-a", "[%-a]"},
	{"%+a", "[%+a]"},
	{"%#a", "[%#a]"},
	{"% a", "[% a]"},
	{"%0a", "[%0a]"},
	{"%1.2a", "[%1.2a]"},
	{"%-1.2a", "[%-1.2a]"},
	{"%+1.2a", "[%+1.2a]"},
	{"%-+1.2a", "[%+-1.2a]"},
	{"%-+1.2abc", "[%+-1.2a]bc"},
	{"%-1.2abc", "[%-1.2a]bc"},
}

func TestFlagParser(t *testing.T) {
	var flagprinter flagPrinter
	for i, tt := range flagtests {
		s := Sprintf(tt.in, &flagprinter)
		if s != tt.out {
			t.Errorf("%d. Sprintf(%q, &flagprinter) => %q, want %q", i, tt.in, s, tt.out)
		}
	}
}

Note the detailed error message provided with t.Errorf: The test case index, the name of the function tested, its inputs, result, and expected result are provided. When the test fails it is immediately obvious which test failed and why.

Helper functions for writing tests

Sometimes it is convenient to use additional helper functions that validate a test result, for instance because the same validation code can be used repeatedly, or because the test cases don't lend themselves easily to a table driven test.

A good helper function also provides detailed information about failing tests, so that is easy to see what went wrong. For instance, one might have written the code above as:

func verify(t *testing.T, testnum int, testcase, input, output, expected string) {
	if input != output {
		t.Errorf("%d. %s with input = %s: output %s != %s", testnum, testcase, input, output, expected)
	}
}
 
func TestFlagParser(t *testing.T) {
	var flagprinter flagPrinter
	for i, tt := range flagtests {
		s := Sprintf(tt.in, &flagprinter)
		verify(t, i, "Sprintf", tt.in, s, tt.out)
	}
}

Resist the temptation of using helper functions such as assert or checkEq or the like that only print failed and provide no further information. In our experience they lead to shortcuts when writing test cases, and as a result to less good tests.

References

Powered by Google Project Hosting