One of the nicest things about working with Go is its powerful suite of testing tools. Unlike other languages, which usually delegate the development of testing functionality to third-parties, Go’s standard testing package and the go test CLI are usually more than sufficient to rigorously test your application. That said, while working on large Go projects I’ve discovered a number of useful but lesser-known patterns that can make rigorously testing Go applications even better.

Pattern 1: Custom Test Filters via CLI Flags

When go test creates your test binary’s main method, it calls flags.Parse() in order to extract the command-line flags you pass it. As a result, you can also define application-specific flags and use them as part of your tests. This is a powerful way of filtering which tests you would like to run. Consider the following code, extracted from go-filecoin:

import (
	"flag"
	"testing"
)

var integrationTest = flag.Bool("integration", false, "Run integration tests")
var unitTest = flag.Bool("unit", true, "Run unit tests")

func IntegrationTest(t *testing.T) {
	if !*integrationTest {
		t.SkipNow()
	}
	t.Parallel()
}

func UnitTest(t *testing.T) {
	if !*unitTest {
		t.SkipNow()
	}
	
	t.Parallel()
}

Now, individual tests can be annotated with their test type by calling testflags.IntegrationTest(t) or testflags.UnitTest(t), and filtered by appending -integration or -unit to the go test invocation. Annotated tests are also made to run in parallel, which is a generally accepted good practice.

The drawback of this approach is that every test will need to call one of these test flag methods. In practice I haven’t found this to be an issue since code review generally prevent un-annotated tests from slipping through. Ideally it would be possible to require test annotation as part of the test’s execution, but I have not discovered a way of doing this just yet.

Pattern 2: *_test Packages

Until last year, the ability to create *_test packages wasn’t mentioned in Go’s official documentation. Their stated purpose is to allow for black-box testing of a package’s public methods without access to its internals. I’ve also found them useful for integration tests.

For example, let’s consider a package that will be managed through an IOC container such as wire. An integration test in this scenario might use the container to create whatever services the package exposes as well as its dependencies. Since creating the container might be complex, it would make sense to extract the logic into a testutils package. This creates a circular dependency, however: the test would require the test utility, which would then require the package under test. Placing the tests in a *_test package solves this problem while still somewhat adhering to the Go mantra of “place test files in the same package as the one being tested.”

Note that while *_test packages are a powerful tool they should be used sparingly and have some sometimes odd behavior. Unlike regular test files, private methods implemented in *_test packages are not exposed to other files in the package. Similarly, overuse of *_test packages to break circular dependencies usually implies a poorly-factored application.