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) PASS ok
So now we have a test suite easily extended not only by new
List implementations, but also with new agnostic interface-based test methods.