Why do Go tests keep fixture files in a directory named testdata, and how does the go command treat it?
answer
- a directory name the toolchain knows
- same club as dot and underscore prefixes
- never compiled, never vetted
- relative paths work because of the cwd rule
- a .go file can live there safely
basics
~20 sThe go command ignores directories named testdata when matching package patterns, so nothing inside is compiled or vetted. A test binary runs with its own package directory as the working directory, so a relative path like testdata/user.go.golden always resolves.
solid answer
~50 s`testdata` is a convention the `go` command enforces: when it expands a pattern like `./...` it skips directories named `testdata`, exactly as it skips names starting with `.` or `_`. So the files there are never compiled and never vetted, which is what makes it safe to store a golden file that happens to be Go source — a code generator's expected output can sit there as `user.go.golden` without `go build ./...` trying to build it, and without it needing to be a valid package. The second guarantee comes from `go test`: each test binary runs with the working directory set to the source directory of the package under test, so `os.ReadFile(filepath.Join("testdata", "user.go.golden"))` resolves the same way locally and in CI. Nothing in the `testing` package knows about `testdata`; it is just a directory name plus those two guarantees.
go deeper
Be ready to say the two facts plainly: the go command skips directories named testdata when matching patterns, and a test runs in its own package directory so a relative path to a fixture just works.
Explain why a golden file holding Go source must live there — nothing under testdata is compiled or vetted, so incomplete or conflicting source is inert. Mention the naming habit of a .golden suffix.
Show you have thought about what the convention does not give you: stale goldens nobody reads, huge single-file fixtures that make review useless, and the fact that byte equality asserts spacing and the trailing newline too.
Own the layout convention across a repo: one fixture per case, a predictable path derived from the test name, and an agreement that regenerated fixtures are reviewed as diffs rather than rubber-stamped.
## What `testdata` actually is `testdata` is not a feature of the `testing` package. No function takes it as an argument and no import mentions it. It is a **directory name the `go` command treats specially**, plus a working-directory guarantee that `go test` gives every test binary. Those two facts together are why every Go project that has fixtures puts them in a directory called `testdata`. ## Guarantee 1: the go command skips it when matching patterns When the `go` command expands a package pattern such as `./...`, it walks the file tree and ignores directories it considers non-package directories. Three kinds are ignored: names beginning with `.`, names beginning with `_`, and the name `testdata`. The consequence is that `go build ./...`, `go vet ./...` and `go test ./...` never look inside `testdata`. The contents do not have to be valid Go, do not have to compile, and do not have to belong to any package. That matters enormously for the case this leaf is about: a **code generator** whose test asserts on the Go source it emits. The expected output is Go source, and if it lived in an ordinary directory the toolchain would try to build it — and would fail, because it may declare a package that conflicts with its neighbours, reference imports that are not present, or be deliberately incomplete. Under `testdata` it is inert bytes. A common naming habit follows from that: call the file `user.go.golden` or `user.golden` rather than `user.go`. It is not required — nothing under `testdata` is compiled either way — but it stops text editors from trying to interpret the file as a live source file in the module. ## Guarantee 2: the working directory `go test` compiles a test binary and runs it **with the working directory set to the source directory of the package being tested**. This is a documented guarantee, and it is the whole reason relative fixture paths are idiomatic in Go: ```go want, err := os.ReadFile(filepath.Join("testdata", "user.go.golden")) ``` That single line behaves identically when you run `go test ./codegen` from the module root, `go test .` from inside the package, or `go test ./...` in CI. There is no need for a "find the project root" helper, no environment variable, and no absolute path baked into the test. Use `filepath.Join` rather than a hard-coded `"testdata/user.go.golden"` if you care about Windows, though the forward-slash form also works there in practice. ## How this is used for golden files The pattern is deliberately dumb: 1. Run the thing under test and capture its output as `[]byte`. 2. Read the corresponding file from `testdata`. 3. Compare the two byte slices with `bytes.Equal`. 4. On mismatch, fail with a message naming the file. Because step 3 is a byte comparison, everything about the output is asserted at once — the field order a generator chose, the blank lines between declarations, the gofmt indentation, and the trailing newline at the end of the file. That is the strength of golden files (a huge assertion for one line of test code) and their weakness (a one-byte change turns the whole file red). ## Practical layout One file per case, named after the case, is the layout that keeps the diffs readable: ``` codegen/ generate.go generate_test.go testdata/ user.go.golden order.go.golden ``` Some suites nest a directory per case with an input and an expected output side by side; that works equally well since the whole subtree is ignored by the toolchain. ## What testdata does not do It does not hide the files from git — they are ordinary repository content, committed and reviewed like any other file, and that is the point: the review diff of a regenerated golden is where the assertion is actually checked by a human. It does not exclude them from your module either. And it gives you no help with staleness: a golden file that no test reads any more sits there silently until somebody notices.
- Where exactly does a test binary's working directory point, and why does that matter for fixtures?`go test` runs each test binary with the working directory set to the source directory of the package under test. That is what makes `os.ReadFile("testdata/user.go.golden")` resolve identically whether you run `go test .` inside the package, `go test ./codegen` from the module root, or `go test ./...` in CI. Without that guarantee every fixture read would need to locate the project root first.
- Which other directory names does the go command skip when expanding a pattern like ./...?Directories whose names begin with `.` or `_` are skipped along with `testdata`. That is why `_scratch` or `.tmp` directories are also invisible to `go build ./...`. The rule is about package pattern matching only — the files still exist, are still committed, and can still be read at runtime by a test.
- Can you use //go:embed to bake a golden file into the test binary instead of reading it at runtime?You can — the embed patterns only refuse names beginning with `.` or `_`, and `testdata` sits inside the package directory. But it works against the golden-file workflow: an embedded copy is fixed at compile time, so an `-update` flag still has to write to the file on disk, and you end up maintaining two paths to the same bytes. Plain `os.ReadFile` is the simpler default.
It is the props cupboard backstage: everything in it is part of the production and travels with it, but the stage crew never tries to act it out.
saying these in an interview costs you the question
- Thinks the testing package has a testdata API
- Believes files under testdata are compiled and must parse
- Computes an absolute path or walks up to find the repo root
- Assumes testdata is excluded from the repository or from the module
- Thinks go vet still checks Go files stored under testdata