Testing interface implementations with subtests in Go

Running separate tests for different interface implementations can be tricky in Go. Using subtests can streamline the process by easily reusing tests in each implementation.

Let's say we have a pretty basic List interface:

For this interface, we can think of creating some agnostic tests for each of those four methods. That should be easy, so we code the following testing functions:

Function names begin with a lowercase letter so the Go test engine doesn't try to run them automatically. Having a separate uppercase test function for each method, just calling the agnostic version with a particular implementation doesn't look like a good solution, and it will be tedious to expand for new ones.

With subtests, we can create some kind of test case suite definition to run them all as a reusable unit:

Even if the function is still a little verbose, the Run method lets us create our own test plan for all implementations in one block, without having the need to define separate functions for each, but still having the capacity of uniquely naming each run. It also allows you to control parallelism in tests execution but that's another thing.

We're also taking advantage of the easy way Go lets you create new types, so the runListSubtests function will work with the fact that the listBuilder builder function being passed will return a particular List implementation. We're now ready to define the entry points for each implementation tests:

Those test functions execute our crafted suite for each implementation, passing an anonymous function that returns a particular type of list:

$ go test -v .
=== RUN   TestArrayList
=== RUN   TestArrayList/Append
=== RUN   TestArrayList/Get
=== RUN   TestArrayList/IndexOf
=== RUN   TestArrayList/Length
--- PASS: TestArrayList (0.00s)
    --- PASS: TestArrayList/Append (0.00s)
    --- PASS: TestArrayList/Get (0.00s)
    --- PASS: TestArrayList/IndexOf (0.00s)
    --- PASS: TestArrayList/Length (0.00s)
=== RUN   TestLinkedList
=== RUN   TestLinkedList/Append
=== RUN   TestLinkedList/Get
=== RUN   TestLinkedList/IndexOf
=== RUN   TestLinkedList/Length
--- PASS: TestLinkedList (0.00s)
    --- PASS: TestLinkedList/Append (0.00s)
    --- PASS: TestLinkedList/Get (0.00s)
    --- PASS: TestLinkedList/IndexOf (0.00s)
    --- PASS: TestLinkedList/Length (0.00s)

So now we have a test suite easily extended not only by new List implementations, but also with new agnostic interface-based test methods.