DEV Community

Alex Pliutau
Alex Pliutau

Posted on • Updated on

Table driven tests in Go

Table driven tests in Go

In practice-go we often use table driven testing to be able to test all function scenarios. For example the FindAnagrams() function returns us a list of anagrams found in the dictionary for given input. To be able to test this function properly we need to test multiple cases, like empty input, valid input, invalid input, etc. We could right different asserts to make it, but it's much more easier to use table tests.

Imagine we have this function:

FindAnagrams(string word) []string
Enter fullscreen mode Exit fullscreen mode

Here is how our table may look like:

var tests = []struct {
    name string
    word string
    want []string
}{
    {"empty input string", "", []string{}},
    {"two anagrams", "Protectionism", []string{"Cite no imports", "Nice to imports"}},
    {"input with space", "Real fun", []string{"funeral"}},
}
Enter fullscreen mode Exit fullscreen mode

Usually table is a slice of anonymous structs, however you may define struct first or use an existing one. Also we have a name property describing the particular test case.

After we have a table we can simply iterate over it and do an assertion:

func TestFindAnagrams(t *testing.T) {
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := FindAnagrams(tt.word)
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("FindAnagrams(%s) got %v, want %v", tt.word, got, tt.want)
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

You may use another function instead of t.Errorf(), t.Errorf() just logs the error and test continues.

The testify package is very popular Go assertion package to make unit tests clear, for example:

assert.Equal(t, got, tt.want, "they should be equal")
Enter fullscreen mode Exit fullscreen mode

t.Run() will launch a subtest, and if you run tests in verbose mode (go test -v) you will see each subtest result:

=== RUN   TestFindAnagrams
=== RUN   TestFindAnagrams/empty_input_string
=== RUN   TestFindAnagrams/two_anagrams
=== RUN   TestFindAnagrams/input_with_space
Enter fullscreen mode Exit fullscreen mode

Since Go 1.7 testing package enables to be able to parallelize the subtests by using (*testing.T).Parallel(). Please make sure that it makes sense to parallelize your tests!

t.Run(tt.name, func(subtest *testing.T) {
    subtest.Parallel()
    got := FindAnagrams(tt.word)
    // assertion
})
Enter fullscreen mode Exit fullscreen mode

That's it, enjoy writing table driven tests in Go!

Discussion (4)

Collapse
madislohmus profile image
Madis Lõhmus

Tabel driven tests in go are awesome. I like that they also allow you to run each sub-test separately with go test -run "TestFoo".

A small typo in the article: got != tt.want shouldn't compile as you can compare a slice to nil only.

Collapse
plutov profile image
Alex Pliutau Author

Oh, you are right. DeepEqual can be used instead. I modified the code example.

Collapse
radlinskii profile image
radlinskii • Edited on

Thank you for sharing this post! It helped me a lot. 🙌

Collapse
forstmeier profile image
John Forstmeier

I also find test tables helps me identify what exactly the function should be accepting/returning. It's part of my construction process.