What does declaring `package foo_test` in a _test.go file change versus declaring `package foo`?
answer
- two packages, one directory
- only _test.go files may differ
- one of them has to import the other
- only capitalised names cross the line
- black box versus white box, chosen per file
basics
~20 sA _test.go file declaring package foo compiles into the package itself and can use its unexported identifiers. One declaring package foo_test compiles as a separate package that must import foo and sees only its exported API.
solid answer
~50 sGo normally forces every file in a directory to share one package name; `go test` allows exactly one extra name, the package's name plus a `_test` suffix, and only in `_test.go` files. Files that say `package foo` are compiled *into* foo, so they can call unexported functions and read unexported fields. Files that say `package foo_test` form a separate external test package that has to `import` foo by its full path and can only touch exported identifiers. Both kinds can sit in the same directory: `go test` compiles foo (augmented with its internal test files), then `foo_test`, then a generated main package, and links all of it into one test binary, so `-run`, coverage and flags span both. The external form buys you a black-box view, honest feedback about the exported API, and an escape from import cycles.
code
go · 9 lines// geom/normalize_test.go
package geom
func TestNormalize(t *testing.T) {
// normalize is unexported and directly visible here.
if got := normalize(3, 4); got != 5 {
t.Fatalf("normalize(3, 4) = %v, want 5", got)
}
}go deeper
Be ready to state the visibility rule in one sentence: a _test.go file saying package foo is inside the package and sees unexported names, one saying package foo_test is outside and must import it. Know that the _test suffix is the only extra package name allowed.
Explain what go test actually compiles for the directory: the augmented package, the external test package, and the generated main, all linked into one binary. Say why the restricted view is useful rather than merely stricter.
Show judgment about which tests belong on which side, and name the failure modes: tests coupled to internals that break on behaviour-preserving refactors, and import cycles that only the external form resolves.
Frame it as a contract question: the external test package is the first consumer of an API other teams will depend on, so treat friction there as a design signal rather than a test-writing inconvenience.
## The one exception to "one directory, one package" Every ordinary `.go` file in a directory must declare the same package clause. The `go` tool makes a single exception, and only for files whose names end in `_test.go`: such a file may declare either the directory's package name (`foo`) or that name with a `_test` suffix appended (`foo_test`). No other name is accepted — `foo_tests`, `footest` or `testing_foo` are compile errors. Those two forms have names: an *internal* test file (`package foo`) and an *external* test package (`package foo_test`). ## What `go test` actually builds For one directory, `go test` can compile up to three packages: 1. **foo** — the ordinary `.go` files *plus* every `_test.go` file that declares `package foo`. This is a special, augmented build of foo that exists only inside the test binary; the shipped package never contains the test files. 2. **foo_test** — built from the `_test.go` files that declare `package foo_test`. It is a normal package in every respect except that nothing can import it: it exists only for this test binary. 3. A generated `main` package that enumerates the `TestXxx`, `BenchmarkXxx`, `FuzzXxx` and `ExampleXxx` functions found in *both* of the above and hands them to the test runner. All three link into one binary. That matters in practice: a `-run` regexp filters test functions from both packages at once, `-cover` reports one coverage figure for the package under test, and there is one process, one set of flags, one exit code. ## Visibility — the whole point An internal test file *is* foo, so it can call `normalize`, mutate the package-level `indexCache`, or construct a struct with unexported fields directly. That is white-box testing: the test sees the implementation. An external test file is a different package. To touch anything it must write `import "example.com/mod/geom"` and then qualify every reference — `geom.Rect`, `geom.NewIndex` — and it can only reach identifiers whose names start with an upper-case letter. Nothing else is visible: not unexported functions, not unexported struct fields, not unexported methods. That is black-box testing: the test sees exactly what a real consumer sees. ## Why you would want the restricted view **API feedback.** The external test is the first consumer of your package, and it is written before any real caller exists. If the test has to construct four values and call three setters in the right order to do one useful thing, the API is awkward and you find out immediately rather than after twelve teams have imported it. **Refactor tolerance.** Tests that only exercise the exported contract keep passing when you rename an internal helper, split an unexported type in two, or replace a linear scan with an index. Tests wired to internals fail on changes that did not change behaviour, and that noise slowly teaches a team to distrust the suite. **Import cycles.** A `package foo` test file's imports become imports of foo itself in the test build. If you import a shared fixture package that itself imports foo, the graph has a cycle and the build fails. The external test package is *downstream* of both foo and the fixture package, and nothing imports it, so the cycle disappears. This is why several standard-library packages test themselves externally — `net/http`'s tests are `package http_test` so they can use `net/http/httptest`, which imports `net/http`. **Documentation.** `ExampleXxx` functions written in the external test package read like real user code, because they are: full import path, exported names, nothing an outsider could not write. ## What it costs Anything genuinely unexported becomes unreachable. A parser's state machine, an eviction path in an internal cache, an error branch that only triggers when an internal counter overflows — none of that can be driven from outside. The conventional answer is a small `export_test.go` file (declaring `package foo`, so also test-only) that assigns the few internals you truly need to exported names; or simply keeping a short internal test file next to the external ones. Mixing both in a directory is normal and idiomatic. ## Common misreadings The `_test` suffix is part of the *package clause*, not the directory; there is no separate directory involved. The external test package is not "optional" or gated behind a flag — `go test` always builds it if such files exist. And the two forms are not exclusive: the same directory routinely holds `foo_test` files for the contract and one `package foo` file for the internals.
- Can a directory hold both `package foo` and `package foo_test` test files at the same time?Yes, and it is common. `go test` compiles foo augmented with its internal test files, compiles foo_test separately, generates a main package listing test functions from both, and links one binary. Teams usually keep the contract tests external and one small internal file for invariants that cannot be reached from outside.
- Can another package import foo_test to reuse a helper defined there?No. An external test package is built only by `go test` for that one directory and has no importable identity — its identifiers are private to that test binary. Shared helpers belong in a normal package (often under `internal/`) or in a `testdata`-free helper package that the external tests import.
- Which package names are legal in a _test.go file in a directory whose package is foo?Exactly two: `foo` and `foo_test`. The `_test` suffix must be appended to the real package name, and only files ending in `_test.go` may use it. Any other name is a build error, and non-test files may never use the suffixed form.
saying these in an interview costs you the question
- Says a directory can only ever contain one package name
- Thinks package foo_test can still see unexported identifiers
- Believes external tests must live in a separate directory
- Claims go test skips foo_test files unless a flag is passed
- Thinks the two forms are mutually exclusive in one directory