Question

Go Tests in Subdirectories: How Go Testing Works and Where Test Files Belong

gounit-testingbuild

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 as package 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 test can 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/ or examples/

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.

Take Quiz

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, 3)
	want := 5

	if got != want {
		t.Fatalf("Add(2, 3) = %d; want %d", got, want)
	}
}

Example function

package mathutil

import "fmt"

func ExampleAdd() {
	fmt.Println(Add(2, 3))
	// Output: 5
}

What happens if you move tests to a subdirectory?

mathutil/
  add.go
  tests/
    add_test.go

Now tests/ is treated as its own package directory. The test file would need to import mathutil like a separate package:

package tests

import (
	"testing"

	"yourmodule/mathutil"
)

func TestAdd(t *testing.T) {
	got := mathutil.Add(2, 3)
	if got != 5 {
		t.Fatalf("got %d, want 5", got)
	}
}

This works as a separate testing package, but it is no longer the normal unit-test layout for mathutil.

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

  1. Go looks at the current directory.
  2. It collects all normal .go files for the package.
  3. It also collects all files ending in _test.go.
  4. It builds a test binary for that package.
  5. It finds functions that match the test pattern, such as TestAdd.
  6. It runs TestAdd.
  7. Inside TestAdd, Add(1, 2) returns 3.
  8. got equals want, so the test passes.

Important detail

If add_test.go were moved into a subdirectory, Go would not treat it as part of the original package's directory anymore. You would then need to test through an import path instead of directly as a same-directory package test.

Real World Use Cases

Here is how this concept appears in real Go projects.

Unit tests beside package code

Most Go packages use:

  • service.go
  • service_test.go
  • parser.go
  • parser_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

Go commonly uses a testdata/ directory:

mypkg/
  parser.go
  parser_test.go
  testdata/
    input.json
    expected.json

testdata/ is a special convention: Go tooling ignores it as a package, but tests can read files from it.

Take Quiz

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.

5. Store reusable helpers carefully

Some projects add helper code such as:

internal/testutil/

This keeps shared test setup logic separate without moving the actual package unit tests away from the package they belong to.

Common pattern summary

  • Unit tests: same directory
  • Examples for documentation: same directory in _test.go
  • Integration or end-to-end tests: separate directories are reasonable
  • Fixtures and sample files: testdata/
  • Example applications: separate examples/ or cmd/ folders

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 ExampleAdd in another folder and expect it to document the original package.

How to avoid it:

  • Keep documentation examples in the package directory, usually in an _test.go file.

Mistake 4: Creating a cleaner layout that fights the tooling

Some developers coming from other languages try to make layouts like this:

src/
  mypkg/
tests/
  mypkg/

This is not idiomatic Go for package unit tests.

How to avoid it:

  • Let Go's package-per-directory model drive the layout.
  • Optimize for go test, readability, and maintainability instead of forcing another ecosystem's structure.

Mistake 5: Forgetting about testdata/

Sometimes people create helper directories for fixture files and accidentally make them look like packages.

Better approach:

mypkg/
  parser.go
  parser_test.go
  testdata/
    sample.txt

Use testdata/ for input/output files used by tests.

Comparisons

ApproachLocationBest forCan access unexported identifiers?Typical use
Same-package testSame directory, package mypkgUnit testing internals and public codeYesMost common unit tests
External testSame directory, package mypkg_testTesting public API as a user wouldNoAPI-focused tests
Test in subdirectoryDifferent directoryIntegration or separate-package testingNo, unless exported through APIBroader tests across packages
testdata/ directoryInside package directoryFixture filesNot applicableSample JSON, text, expected outputs
examples/ or cmd/ directoryDifferent directoryRunnable demo programsNo, through imports onlyTutorials and sample apps

Same directory vs subdirectory

  • Same directory is the normal choice for package tests.
  • Subdirectory is a separate package context, not a cleaner version of the same thing.

Convention vs rule

It is both:

  • partly a convention because Go developers prefer it
  • partly a tooling rule because go test works package-by-directory
Take Quiz

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.

Take Quiz

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.go file 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.
Take Quiz

Keep learning

Related questions

View more