Question
Go Tests in Subdirectories: How Go Testing Works and Where Test Files Belong
Question
I want to create a Go package and keep its tests and examples in subdirectories so the workspace stays cleaner. Is that possible, and if so, how would it work?
Most documentation places test code in the same directory as the package source files. Is that recommended for a technical reason, or is it only a convention?
Short Answer
By the end of this page, you will understand how Go organizes packages, how go test discovers tests, why test files are usually stored in the same directory as the code they test, and what alternatives exist when you want cleaner project structure. You will also see when subdirectory-based tests make sense and when they do not.
Concept
In Go, directory structure and package structure are closely related. This is the key reason test files are usually placed in the same directory as the code under test.
A Go package is normally defined by the files in a single directory. When you run go test, Go builds and tests that package from that directory. Test files are discovered when they:
- are in the same directory as the package files, and
- use filenames ending in
_test.go
This matters because Go testing is designed around testing a package as it is built. Tests are not treated as a completely separate project layout system.
There are two common kinds of test files:
- Internal tests: use the same package name, such as
package mathutil - External tests: use the package name with
_test, such aspackage mathutil_test
Even external tests usually live in the same directory. They are still associated with that package directory.
Why this matters
Keeping tests beside the code gives Go a few advantages:
- The tooling stays simple.
go testcan automatically discover tests.- Refactoring is easier because code and tests stay together.
- Package boundaries remain clear.
So this is not just convention. It is strongly connected to how Go's package system and test tooling work.
Can tests be in subdirectories?
Yes, but not in the way many beginners first expect.
If you put tests in a subdirectory, that subdirectory becomes a different package directory. That means:
- the tests are no longer part of the original package directory
- they cannot act like normal same-package tests
- they must import the original package if they want to test it
Example...functions there will document that subpackage, not the parent package
So while subdirectories are possible, they are usually used for:
- integration tests
- test fixtures
- helper packages
- separate example programs in
cmd/orexamples/
They are generally not used for normal unit tests of a package.
Mental Model
Think of a Go package as a folder-based workshop.
- The package source files are the tools and materials inside the workshop.
- Test files are inspectors checking the tools.
- Go expects those inspectors to work inside the same workshop.
If you move the inspectors into another room downstairs, they are no longer naturally part of that workshop. They can still inspect it, but now they must enter from the outside, like visitors. That changes what they can access and how they are discovered.
So in Go, placing tests in the same directory is like keeping the instructions and inspection checklist right next to the thing being built.
Syntax and Examples
In Go, normal test files live beside the source files.
Standard package layout
mathutil/
add.go
add_test.go
example_test.go
Source file
package mathutil
func Add(a, b int) int {
return a + b
}
Test file in the same directory
package mathutil
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Fatalf("Add(2, 3) = %d; want %d", got, want)
}
}
External test in the same directory
package mathutil_test
import (
"testing"
"yourmodule/mathutil"
)
func TestAdd(t *testing.T) {
got := mathutil.Add(2, )
want :=
got != want {
t.Fatalf(, got, want)
}
}
Step by Step Execution
Consider this package:
package mathutil
func Add(a, b int) int {
return a + b
}
And this test:
package mathutil
import "testing"
func TestAdd(t *testing.T) {
got := Add(1, 2)
want := 3
if got != want {
t.Fatalf("got %d, want %d", got, want)
}
}
What happens when you run go test
- Go looks at the current directory.
- It collects all normal
.gofiles for the package. - It also collects all files ending in
_test.go. - It builds a test binary for that package.
- It finds functions that match the test pattern, such as
TestAdd. - It runs
TestAdd. - Inside
TestAdd,Add(1, 2)returns .
Real World Use Cases
Here is how this concept appears in real Go projects.
Unit tests beside package code
Most Go packages use:
service.goservice_test.goparser.goparser_test.go
This is the standard and most practical layout for:
- utility packages n- business logic
- parsing and validation
- API helpers
- data transformation functions
Integration tests in separate directories
Sometimes teams create subdirectories for broader tests, for example:
project/
internal/app/
service.go
service_test.go
integration/
api_test.go
This can be useful when tests:
- start servers
- call databases
- depend on configuration files
- cover multiple packages together
Example programs
If you want demonstration programs rather than Go doc examples, a separate directory can make sense:
project/
mypkg/
lib.go
lib_test.go
examples/
basic/main.go
That is different from Example... test functions, which are usually kept with the package.
Test data files
Real Codebase Usage
In real codebases, developers usually follow these patterns.
1. Keep unit tests next to the code
This is the default because it supports:
- automatic discovery with
go test - easy navigation in editors
- package-level testing
- simpler maintenance during refactoring
2. Use package name_test for public API testing
Developers often write some tests as external tests to ensure they only use exported functions:
package mathutil_test
This helps verify that the package is usable from outside, just like a real consumer would use it.
3. Use internal tests for unexported behavior
If you need access to unexported functions or package internals, use the same package name:
package mathutil
4. Put integration tests in dedicated packages or folders
Large projects may have folders like:
integration/e2e/cmd/...testdata/
These are not replacements for ordinary unit tests. They are additional structure for different types of testing.
Common Mistakes
Mistake 1: Assuming subdirectories are part of the same package
Broken expectation:
mypkg/
thing.go
tests/
thing_test.go
A beginner may expect tests/thing_test.go to behave like a normal package test for mypkg. It does not. tests/ is a different directory, so it is treated separately.
How to avoid it:
- Put normal unit tests in the same directory as the package.
- Use subdirectories only when you intentionally want a separate package or broader test setup.
Mistake 2: Using the wrong package name in a subdirectory
Broken code:
package mypkg
inside mypkg/tests/add_test.go
This declares package mypkg, but the directory is still different. That usually leads to confusion and build issues.
How to avoid it:
- In a different directory, treat it as a different package.
- Import the real package by module path if needed.
Mistake 3: Moving example tests away from the package
Broken idea:
- Put
ExampleAddin another folder and expect it to document the original package.
Comparisons
| Approach | Location | Best for | Can access unexported identifiers? | Typical use |
|---|---|---|---|---|
| Same-package test | Same directory, package mypkg | Unit testing internals and public code | Yes | Most common unit tests |
| External test | Same directory, package mypkg_test | Testing public API as a user would | No | API-focused tests |
| Test in subdirectory | Different directory | Integration or separate-package testing | No, unless exported through API | Broader tests across packages |
testdata/ directory | Inside package directory | Fixture files |
Cheat Sheet
Go testing quick reference
- Test files must end with
_test.go - Go discovers tests per directory/package
- Normal unit tests usually live in the same directory as the code
- Same package test:
package mypkg- can access unexported identifiers
- External package test:
package mypkg_test- tests only exported API
- Subdirectories are separate packages, not the same package's test area
- Use
testdata/for fixture files - Keep
Example...functions with the package if they are meant for package documentation
Basic commands
go test
go test ./...
go test -run TestAdd
Typical layout
mypkg/
file.go
file_test.go
example_test.go
testdata/
Rule of thumb
- Want to unit test one package? Keep tests in the same directory.
- Want broader integration tests or demo apps? Separate directories can make sense.
FAQ
Can Go tests be placed in a subdirectory?
Yes, but that subdirectory is treated as a separate package directory. It is not the normal location for unit tests of the parent package.
Why does Go usually keep tests beside the source files?
Because Go's tooling is package-and-directory based. Keeping tests in the same directory makes discovery, compilation, and maintenance simpler.
Is putting tests next to code just a convention?
Not only a convention. It is also aligned with how go test works and how Go defines packages by directory.
Can tests in a subdirectory access unexported functions?
No, not as normal package tests. Since they are in a different directory, they must usually import the package and can only use exported identifiers.
Where should Go examples go?
If they are documentation examples using Example..., keep them in _test.go files in the package directory. If they are standalone demo programs, use examples/ or cmd/.
What is testdata/ used for in Go?
It is the conventional place for files used by tests, such as sample JSON, input text, or expected outputs.
Should integration tests be in the same directory too?
Sometimes yes, but many projects place larger integration tests in separate directories if they test multiple packages or need special setup.
Mini Project
Description
Create a small Go package with one function, unit tests in the package directory, and fixture data in testdata/. This project demonstrates the idiomatic Go layout and helps you see why tests usually live next to the code they verify.
Goal
Build and test a Go package using the standard same-directory test layout.
Requirements
- Create a package with one exported function.
- Add at least one
_test.gofile in the same directory. - Add a
testdata/folder with one sample file. - Write a test that reads the sample file and checks the function output.
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.