Run integration tests separately

Integration tests are great. They allow you to test your system in more realistic scenarios and loads, catch subtle bugs between system boundaries, and help enforce a mindset of simple and fast deployments.

On the other hand, they’re usually more resource-hungry, need a specific environment setup to run, and are prone to flakiness, so it makes sense to be able to run them selectively.

Let’s go!

With build constraints

A good first step is to separate integration tests into different XYZ_integration_test.go files.

Separating your tests like this enables selection by using tags to constrain builds.

If you include a // +build integration as the top-line of your test files, they will only be compiled when passing the appropriate tag as
go ./... test -v -tags integration

These build constraints can be more complex, including full boolean formulas.
You can define things as

// +build go1.15 
// +build darwin linux,!aws

package myawesomepackage

This file will be built only if Go version is 1.15 or higher, and if either the platform is Darwin or is the platform is Linux and the aws tag has not been passed.

By using the -short flag

The testing package features a -short flag.

This allows to tag and disable your integration tests like this

func TestXYZIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration tests in short mode.")

Then, you’d either use go test -v ./... -short to only run the unit tests, or go test ./... to run the full suite.

With a CLI flag

The opposite can happen as well, by defining a custom flag which will enable integration tests.

var runIntegration = flag.Bool("integration", false
    , "Run the integration testsuite (in addition to the unit tests)")

func TestXYZIntegration(t *testing.T) {
    if !*runIntegrationTests {
        t.Skip("skipping integration tests")

With an $ENV var

Another option is to use an environment variable. This will allow you to use

func skipIntegration(t *testing.T) {
    if os.Getenv("GO_RUN_INTEGRATION") != "" {
        t.Skip("skipping integration tests")

func TestXYZ(t *testing.T) {

and run your test suite using GO_RUN_INTEGRATION=true go test ./....

Regex Based matching (but please, don’t)

While possible, I’d recommend avoiding regex-base matching.

It imposes specific naming conventions, and makes running tests manually harder, even if you’re using a Makefile.

So, if you name your tests TestUnitXYZ or TestIntegrationXYZ, you can then run go test ./... -run=Unit or go test ./... -run=Integration to run tests whose names match.

Another drawback is that you cannot easily exclude test functions without resorting to constructs like -run "Test[^I][^n][^t][^e][^g][^r][^a][^t][^i][^o][^n].*"

Include a timeout

Finally, don’t forget to include a timeout. The default value, 10 minutes, is in my opinion too large; just fail fast and if any of your tests do need to run for larger periods of time, they should be separated as soon as possible.

Got anything to add? Spotted any mistakes or got any cool stories? Feel free to reach out using email or ping me on Twitter @tpaschalis_!


Written on March 9, 2020