How can I have a common test suite for multiple packages in go?

I don't really dislike the idea to use a separate testing library. If you have an interface and you have generic tests for each interface, other people that implement that interface might like to use these tests as well.

You could create a package "package/test" that contains a function

// functions needed for each implementation to test it
type Tester struct {
    func New() package.Interface
    func (*package.Interface) Done()
    // whatever you need. Leave nil if function does not apply
}

func TestInterface(t *testing.T, tester Tester)

Notice that the signature of TestInterface does not match to what go test expects. Now, for each package package/impl/x you add one file generic_test.go:

package x

import "testing"
import "package/test"

// run generic tests on this particular implementation
func TestInterface(t *testing.T) {
    test.TestInterface(t,test.Tester{New:New})
}

Where New() is the constructor function of your implementation. The advantage with this scheme is that

  1. Your tests are reusable for whoever implements your interface, even from other packages
  2. It is immediately obvious that you run the generic test suite
  3. The test cases are where the implementation is and not at another, obscure place
  4. The code can be adapted easily if one implementation needs special initialization or similar stuff
  5. It's go test compatible (big plus!)

Of course, in some cases you need a more complicated TestInterface function, but this is the basic idea.


Maybe something gets mixed up here a bit: If package a defines an interface only than there is no code to test as interfaces in Go are implementation free.

So I assume the methods in your interface in package a have constraints. E.g. in

interface Walker {
    Walk(step int)
    Tired() bool
}

you contract assumes that Tired returns true if more than 500 steps have been Walk'ed (and false otherwise) and your test code checks these dependencies (or assumption, contracts, invariants whatever you name it).

If this is the case I would provide (in package a) an exported function

func TestWalkerContract(w Walker) error {
    w.Walk(100)
    if w.Tired() { return errors.New("Tired after 100 steps") }
    w.Walk(450)
    if !w.Tired() { return errors.New("Not tired after 100+450 steps") }
}

Which documents the contract properly and can be used by packages b and c with types implementing walker to test their implementations in b_test.go and c_test.go. IMHO it is perfectly okay that these function like TestWalkerContract are displayed by godoc.

P.S. More common than Walk and Tired might be an error state which is kept and reported until cleared/reseted.


If you share a piece of code for reuse by different packages then yes, it is a library by definition. Even when used only for testing from *_test.go files. It's no different from importing "testing" of "fmt" in the _test.go file. And having the API documented by godoc is a plus, not minus IMHO.

Tags:

Go