If you’re going to remember anything from this article, remember this:
go test is a command which automates the execution of test files and functions in
a Go project. The
go test command ships with Go itself, so if you have Go
installed there’s nothing to check - it’s available on your machine.
go test will recompile each package along with any files with names matching
the file pattern
*_test.go files can contain test
functions, benchmark functions, and example functions. Each listed package will
cause the execution of a separate test binary.
go test compiles test files, that declare a package, ending with the suffix
*_test.go as a separate package. It then links them with the main test binary
and runs them.
go test seems simple on the surface, it actually has many options and
commands. It allows for control of what test to be run, as in file-specific or
package-specific tests, has flags for skipping tests, reporting test coverage
and other options. Also, it has smart caching mechanisms under the hood, to
avoids rebuilding packages every time we run our tests.
go test is a compact yet a rich tool that every Gopher should have a solid
command of. We will not look at all its features, but at the end of this
article, we will have a good understanding of the workings of the tool.
go test has two running modes. Understanding them is essential to having an
easy time working with the tool:
- Local directory mode, or running without arguments
- Package list mode, or running with arguments
In the local directory mode,
go test compiles the package sources and
tests found in the current directory and then runs the resulting test binary.
This mode disables caching. After the package test finishes,
go test prints a
summary line showing the test status (‘ok’ or ‘FAIL’), the package name, and
To run your tests in this mode, run
go test in your project’s root directory.
In the package list mode,
go test compiles and tests each of the packages
listed as arguments to the command. If a package test passes,
go test prints
only the final ‘ok’ summary line. If a package test fails, go test prints the
full test output.
To run your test in this mode, run
go test with explicit package arguments.
For example, we can run
go test PACKAGE_NAME to test a specific package or
go test ./... to test all packages in a directory tree or
go test . to run
all tests in the current directory.
In our daily work with
go test, the difference between the two modes is
When we run
go test in package list mode it will cache successful package
test results to avoid unnecessary reruns. When it can find the result of a test
in the cache,
go test will redisplay the cached result instead of running the
tests again. When this happens,
go test will annotate the test results with
(cached) in place of the elapsed time in the summary line.
Test run control
One of the major features of the
go test command is the ability to control
what tests files and test functions we can run. As we discussed before,
go test has two modes to which we can supply package or file names. Additionally,
we can also selectively run one or more test functions.
To have an easier time further in this post, we will define a type
It will have two functions: a constructor
NewPerson and a function
which will take a
*Person and return if one
*Person is older than another
Let’s add tests for the constructor and the
older function. We want to test
that the constructor will return an error when a negative integer is passed as
Let’s add two more tests for the
older function. We will create two
with different ages and check for the return value of the
(If you find the tests a bit confusing I apologise - tried hard to make them more readable but it was hard. I guess that says something about the example.)
The first way to control tests running is by supplying the test files as
arguments to the
go test command. For example, if we have a
person_test.go files, we need to run:
go test command prints only the final
ok, because all the test cases
passed. If we would like to see a more detailed output, we can use the
The second way to run the test for the
person package is to supply its name
go test command:
This way, the
go test locates the package, builds it and runs its tests.
-v flag will produce the same output as before.)
The third way to run tests is by specifying a test function to be run, using
-run flag. The flag takes an argument, which is a regex that will try to
match against any test functions in the current directory. For example, if we
would like to run the
function, we can do this:
If we mistakenly supply a regexp that will not match anything,
go test will
inform us about it:
Often when running tests we want to lower the waiting time and stop at the first test failure we hit. The default mode is to wait for all tests to finish before the errors are reported, so depending on the project this can save us some time.
To do this,
go test has a special flag we can use:
-failfast. Fail fast
does what it advertises: it will stop at the first test that fails, exiting the
process that is running the test suite/files. To see it in action, we will
break one of the test functions for the
older function that we introduced
We know this test will fail:
p1 will be older than
p2, because they are
2 years old respectively. If we run
go test it will run all the
test functions, regardless if some fail:
Now, let’s see the difference if we add the
By adding the
-failfast flag we actually stopped right where we got the first
failure - when running the
TestOlderFirstOlderThanSecond test function. This
option is useful when we do some heavier refactorings of our code. If our
refactor breaks some tests, instead of being overwhelmed by all the errors we
can add the
-failfast flag and fix errors one by one.
Next time you do some big refactors give it a shot, it might save you some sanity as you’re fixing the tests.
Another interesting feature that is packed in
go test is test coverage. Taken
from Go’s website from the blog post on
Test coverage is a term that describes how much of a package’s code is exercised by running the package’s tests. If executing the test suite causes 80% of the package’s source statements to be run, we say that the test coverage is 80%.
We will not go in detail here on how the coverage tools work. I recommend referring to the “Test coverage for Go” section in the aforementioned post to understand how it actually measures the coverage.
If we would like to check the test coverage on our
Person type and its
functions, we can run
go test -cover:
This is great - it means that our tests cover all the functionality of our
functions. To see the coverage tool in action, we can add some more
functionality to the
NewPerson constructor function. Creating a person that
is 1000 years old doesn’t make much sense, especially
that the older person ever recorded was 122 years old. We can extend the
constructor to validate that the newly created
Person instance cannot be
older than 130 years:
If we would run the coverage report again, we will see the following output:
Whoops, our coverage dropped from 100% to 83.3%. To see where our problem is,
we can ask
go test to create a coverage profile for us, using the
This will create a
prof.out file, looking like this:
We don’t have to understand the output here, because Go has another tool that
we can use with the profile file to visualise the coverage report better: its
go tool cover. Using the
prof.out profile file we have, we can
generate an HTML page that will visualise what parts of the code are covered by
This command pops open our browser with a page looking like this:
Looking at the image above, the red coloured code is the one that is never run. In other words, the red code is not covered, while the green one is. The grey code is not tracked, because from the coverage tool perspective it’s boilerplate (read: not needed to be tested).
From the image, it’s clear that our tests never run the new branch where we handle unreasonably old age as the argument. Let’s add another quick test function that will cover this functionality:
go test -coverprofile=hundred.out -v we will see the coverage
go back to 100% again:
Here’s the HTML output of the coverage profile:
go test and
go tool cover tools packs more combined functionality. I
encourage you to read more about it in the official blog
post and to expriment further with the tools.
Software is built to serve highly mutable business requirements, which results in applications that grow and evolve over time. Such applications are (hopefully) well tested, with tests operating on unit, integration and end-to-end levels.
A common technique on bigger projects often is to be explicit what type of
tests we want to run. For example, when running
go test, do we want to
include running the integration tests of the project, or just the unit tests?
In such cases, there are two useful techniques that come out of the box with
Go: build tags and short mode.
-short mode for
go test allows us to mark any long-running tests to be
skipped in this mode.
go test and the
testing package support this via the
testing.Short() functions and the
We can skip a test function by checking if the short mode is on and invoking
t.Skip function if that returns
true. Let’s see this by changing an
If we run the tests normally, using
go test -v we should see no change in the
If we would enable the short mode using the
-short flag, we should see the
change in the behaviour and output:
We can see that the output contains one
SKIP line, meaning in short mode
go test skipped the test function. We can use
-short in other ways, besides
skipping a test. For example, mocking networks calls instead of opening a
connection or loading simple fixtures instead of loading them from a database.
The options are many, it all depends on the function that the test is covering.
Using build tags
go test supports build tags out of the box, like the
-short flag. While we
apply the short mode on the function level, we use the build tags on a file
level. This means that there is no way to use build tags to skip one or a few
functions. Rather, build tags are used to run (or skip) a special type of test
files from our test suite.
There are a couple of rules on build tags. As indicated in this Stack Overflow answer:
- build tags are special comments, with the format
// +build TAGNAME
- we have to place the tag on the first line of the file
- we have to add an empty line after the tag
- the tag name comment cannot have a dash, but it allows underscores
Following these rules, we can add a tag to our
person_test.go file from
person_tests tag is a useless when we have only one file in the test
suite, but we will add it for experimentational purposes.)
To add the build tag to our
go test command, we need to run
go test -tags=TAG_NAME. To run the files with the
person_tests build tag:
Based on the supplied tag,
go test detects the files tagged with
person_tests and runs them. If we would supply a tag that does not exist in
the package, the output would report that no files are found:
Covering all the features that
go test packs in a single post is a hard
undertaking. Here I will mention some other useful features that you should try
to use and read more on:
- List tests with
-list: to list tests, benchmarks, or examples matching a regular expression passed as the argument. The usage of the
-listflags is analogous to the
-runflag that we discussed before. When we use this flag
go testwill not run any tests, benchmarks or examples.
- Disabling caching with
-count: as we discussed before,
go testby default caches test results for packages. It then uses them to skip running tests that have are not modified between two runs. Although can improve the performance, there are times when we would like to disable the caching using the
- Get JSON output using
-json: if we would like to take the output of
go testand process it using a program, having it in a JSON format is better than normal text. This flag will convert the output to JSON, so processing it with a program is less cumbersome.
- Use more CPU cores using
-cpu: this flag will set the
GOMAXPROCSvariable to the argument that we pass to the flag. It limits the number of operating system threads that can execute user-level Go code simultaneously.
- Detect race conditions using
-race: since it’s very easy to write concurrent code in Go, race conditions are always a risk. Go’s tooling is great in this regard - it has a fully integrated race conditions detector in its toolchain. You can read more about it in its announcement blog post.