How go test runs all project tests: under the hood analysis

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

How to run all tests in a Go project and what happens "under the hood"?

\n

Every Go developer has at least once typed the magic command go test ./... in the terminal and watched green checkmarks race across the screen. But have you ever wondered what actually happens at that moment? Why do tests run exactly this way and not otherwise? And why does go test sometimes silently skip your brand new tests?

\n

In this article, we won't just answer a popular question from Stack Overflow (where it has gathered thousands of views), but will look under the hood of the Go compiler and learn how the testing mechanism is structured. Let's go!

\n\n

Why is go test ./... magic?

\n

Let's start with the basics. The go test command without arguments runs tests only in the current package. But if you have a project with dozens of packages (which is the case in real production), manually iterating through them is akin to medieval torture. That's where ./... comes to the rescue.

\n

This pattern means: "find all packages recursively, starting from the current directory." Go traverses the directory tree, ignoring vendor and modules, and collects a list of all packages. For each of them, it launches a separate instance of go test.

\n

But what if you want to run tests only in specific directories? For example, only in ./internal/...? It works the same way: go test ./internal/....

\n

Important: if you are using Go Modules (which is the standard since Go 1.16+), then ./... only works within the current module. To test all modules in a workspace, you'll have to use go test ./... in each of them or configure go.work.

\n\n

What is a "package" in the context of go test?

\n

Go operates with the concept of a package — a group of .go files in one directory. Test files (with the _test.go suffix) are also considered part of the package but are compiled separately. When you run go test, Go compiles a test binary that includes:

\n
    \n
  • The source code of the package (but only what is used in the tests)
  • \n
  • The code of the tests themselves
  • \n
  • A special runtime wrapper for execution and reporting
  • \n
\n

This binary is executed, runs the tests, and outputs the result. The entire process is separate for each package.

\n\n

How does Go decide which tests to run?

\n

When you write go test ./..., Go performs the following steps:

\n
    \n
  1. File system traversal — Go finds all directories containing .go files.
  2. \n
  3. Filtering — directories vendor, testdata, and hidden folders (starting with .) are ignored.
  4. \n
  5. Compilation — a temporary binary is created for each package.
  6. \n
  7. Execution — the binary is run, results are collected.
  8. \n
  9. Caching — if the code hasn't changed, Go uses the cache and shows (cached).
  10. \n
\n

The last point is a common cause of surprise. You changed a test, run go test ./..., and it says "ok (cached)". Why? Because Go caches results based on a hash of the input files. If you only changed a comment or an unused import, the hash doesn't change, and the test is not re-run.

\n

Lifehack: you can force clear the cache with the command go clean -testcache. Or use the -count=1 flag, which forces Go to ignore the cache: go test -count=1 ./....

\n\n

Parallelism: why aren't tests always faster?

\n

By default, go test runs tests within a single package sequentially. But between packages — in parallel! The number of simultaneous packages is determined by the -p flag (default equals the number of CPUs).

\n

Sounds great, but there's a nuance: e

Blogs

Book Recommendations