The Go Programming Language — Testing#
Sampling note (XL tier): 1796 test files across the full
src/tree were surveyed via grep. Deep reads were focused onsrc/strings/strings_test.go,src/runtime/runtime_test.go,src/cmd/go/script_test.go,src/cmd/compile/script_test.go,src/internal/testenv/testenv.go,src/testing/testing.go, and a sample oftestdata/script/*.txtfiles. Counts are derived from whole-tree grep commands.
Test metrics#
- Test files: 1,796
*_test.gofiles insrc/(excl. vendor) - Source files: 5,102 non-test
.gofiles insrc/ - Ratio (test / source): ~1:2.8 — roughly one test file per three source files
- Test frameworks: Stdlib
testingpackage only — zero third-party frameworks (no testify, gomock, ginkgo, gomega, goconvey) - Benchmark functions: 1,944 (
func Bench*) - Example functions: 1,003 (
func Example*) - Fuzz corpus targets: 292 (
func Fuzz*/f.Fuzz(...)) - t.Errorf / t.Fatal calls: 31,077 — entire assertion surface is stdlib
Test organization#
Placement#
Both internal (package foo) and external (package foo_test) test packages are used, frequently in the same directory. The dominant pattern across stdlib is package foo_test (external), which enforces that tests only exercise the public API. A dedicated bridge file — export_test.go — is used when internal state must be accessed.
The export_test.go bridge pattern#
44 files named export_test.go exist across the codebase. These files belong to the production package (e.g., package runtime) but are only compiled during go test, making private identifiers available to the external _test package via aliased vars:
// src/runtime/export_test.go
package runtime
var Fadd64 = fadd64 // exposes internal soft-float fn to runtime_testThis is the canonical Go solution to the black-box / white-box tension: by default, test the public API; when you must, create a curated export file rather than weakening package boundaries permanently.
Helper packages#
internal/testenv— the central cross-platform capability guard library. ProvidesMustHaveGoBuild(t),MustHaveCGO(t),SkipIfShortAndSlow(t),SkipFlaky(t, issue),CPUIsSlow(), etc. Used in 847 test files. Prevents tests from running in environments that cannot satisfy their requirements (no network, no C compiler, WASM runners). Replaces ad-hoct.Skip()strings with well-typed, discoverable conditions.internal/txtar— text archive format. A single.txtfile can embed multiple named sub-files using-- filename --separators. Used by the script test engine to ship multi-file test fixtures in one readable file.cmd/internal/scriptandcmd/internal/script/scripttest— the internal script test engine poweringcmd/go,cmd/compile, andcmd/linkintegration tests (described in detail below).testing/fstest— providesMapFS(an in-memoryfs.FS) andTestFS(a conformance checker forfs.FSimplementations). Any package implementingfs.FScan test conformance with a single call.testing/iotest— provides error-injecting wrappers (OneByteReader,HalfReader,ErrReader) for testing resilience ofio.Reader/io.Writerconsumers.testing/quick— property-based testing via random value generation (frozen; use fuzz testing instead).testing/slogtest— conformance testing forslog.Handlerimplementations.testing/synctest(Go 1.24) — deterministic goroutine and timer testing.synctest.Runcreates a “bubble” where goroutines and timers are controlled by a fake clock, enabling race-free tests of concurrent code withouttime.Sleep.
Fixtures#
- 110
testdata/directories across the codebase. Contents range from binary blobs (archive test cases),.gosource fragments for the compiler,.txtscript tests, and golden output files. - Golden file pattern: A
-updateflag is registered in tests that produce byte-for-byte deterministic output (e.g.,compress/flate,compress/zlib). Runninggo test -updaterewrites the golden files intestdata/rather than failing. Discovered in:compress/flate/huffman_bit_writer_test.go:17. - Embedded corpus: Fuzz test corpus lives in
testdata/fuzz/<FuzzFuncName>/per the standard Go fuzz specification. Found inarchive/tar,archive/zip,image/jpeg,image/png,internal/zstd.
Test patterns#
Table-driven tests#
- Prevalence: Heavy — 2,706 occurrences of
t.Run,testCases,tests :=, ortc.namepatterns across*_test.gofiles - Style: Named struct slices with explicit field tags. The canonical form:
// src/strings/strings_test.go
type IndexTest struct {
s string
sep string
out int
}
var indexTests = []IndexTest{
{"", "", 0},
{"", "a", -1},
...
}
func TestIndex(t *testing.T) {
for _, test := range indexTests {
if actual := Index(test.s, test.sep); actual != test.out {
t.Errorf("Index(%q, %q) = %v; want %v", test.s, test.sep, actual, test.out)
}
}
}- Sub-tests via
t.Run: 797 uses oft.Parallel()combined witht.Runenable fine-grained parallelism at the sub-test level. This is the modern (Go 1.7+) form of table-driven testing. - Example:
src/strings/strings_test.gocontains dozens of named test tables (indexTests,linesTests,splitTests, etc.) — a textbook reference for the pattern.
Mocking approach#
- Strategy: No mocking framework. The Go project relies on two strategies:
- Real implementations in tests — the stdlib tests exercise actual OS, filesystem, and network interfaces rather than mocks.
testenv.MustHaveExternalNetwork(t)guards tests that need a real network. - Interface fakes via
testing/fstest.MapFS— forfs.FS-dependent code, an in-memory map is the fake. Similarly,bytes.Bufferacts as anio.Writermock everywhere.
- Real implementations in tests — the stdlib tests exercise actual OS, filesystem, and network interfaces rather than mocks.
- No generated mocks, no
gomock, nomockery. The small-interface philosophy (io.Reader= one method) makes hand-written fakes trivial.
Fuzz testing#
- 292 fuzz targets across 10+ packages.
- Uses Go 1.18+ native fuzzing:
func FuzzFoo(f *testing.F),f.Add(seed...),f.Fuzz(func(t *testing.T, in []byte) {...}). - Corpus seeds live in
testdata/fuzz/. The fuzzer continuously mutates inputs; without the-fuzzflag,go testruns only the seed corpus (regression mode). - Notable targets:
archive/tar,archive/zip,image/jpeg,image/png,internal/zstd,internal/runtime/maps(hash map implementation).
Parallel tests#
- 797
t.Parallel()calls. Tests that do not mutate global state callt.Parallel()immediately to allow the test binary to run them concurrently. Guarded bytestenv.MustHaveParallelism(t)on single-CPU systems.
Integration tests — the script test system#
The most distinctive testing pattern in this codebase. cmd/go has 916 .txt script test files in testdata/script/. cmd/compile and cmd/link have their own script suites.
Mechanism: Each .txt file is a txtar archive containing:
- Script commands at the top (a miniature shell DSL:
go build,go test,! stderr .,env GOPATH=...) - Embedded source files in
-- filename --sections
# src/cmd/go/testdata/script/work_vendor_main_module_replaced.txt
go work vendor
go list all # consistency checks pass
! stderr .
! go list all # consistency checks fail after edit
stderr 'example.com/b@v0.0.0: is marked as replaced in vendor/modules.txt'
-- go.work --
go 1.21
use (a b)
-- a/go.mod --
module example.com/aThe script engine (cmd/internal/script) executes real go tool binaries inside a temporary module directory, making these true end-to-end tests. TestScript in cmd/go/script_test.go discovers all .txt files and runs each as a t.Run sub-test with t.Parallel(). The compiler version additionally replaces the installed compile binary with the test binary via TestMain, so script tests exercise the binary under test, not a stale installed version.
Separation from unit tests: No separate build tags needed. The script tests run as a normal go test sub-test, gated by testenv.MustHaveGoBuild(t) and testenv.SkipIfShortAndSlow(t).
testing.Short() gating#
- The entire project uses
testing.Short()(viatestenv.SkipIfShortAndSlow(t)) to distinguish fast unit tests from slow integration tests. No-tags integrationbuild tags are used; instead,-test.shortprunes slow tests at runtime.
The export_test.go white-box bridge#
- 44 files expose internal symbols from production packages specifically for test packages
- Runtime’s
export_test.goexposes soft-float functions, GC internals, and scheduler hooks - Used to test the
runtime,strings,sync,net,cryptopackages without weakening encapsulation
Test quality observations#
What’s done well#
Absolute stdlib purity. Zero test dependencies on third-party libraries. Every assertion is via
t.Errorf,t.Fatal, ort.Log. This is not incidental — the Go project is dogfooding its owntestingpackage and proving it sufficient. The result is tests with no transitive dependency graph to manage.The script test system is a major quality investment. 916+ script tests for
cmd/goprovide coverage that pure unit tests cannot: real module resolution, real network caching (mocked viavcstest.NewServer()), real filesystem interactions. The txtar format makes each test self-contained and readable as a specification.First-class testing sub-packages.
testing/fstest,testing/iotest,testing/slogtest,testing/synctesttreat test infrastructure as a shipped library, not an afterthought. Any third-party package implementingfs.FScan usetestfs.TestFSto validate conformance.internal/testenvas capability contract. Instead of ad-hocif runtime.GOOS == "windows"checks scattered throughout tests, all capability checks funnel through named, discoverable functions.SkipFlaky(t, issue)even links to the bug tracker, making flaky test tracking systematic.Fuzz testing as regression suite. Fuzz corpus seeds in
testdata/fuzz/act as a permanent regression set. Any input that previously crashed the parser is now a test case automatically.Benchmarks as documentation of performance contracts. 1,944 benchmark functions serve dual purpose: they measure performance, but they also document which operations are performance-sensitive.
BenchmarkIndex,BenchmarkSplitinstringscommunicate that these must be fast.The
export_test.gopattern elegantly resolves black-box vs. white-box. External_testpackage is the default;export_test.gois the escape valve. This prevents test-only symbols from polluting the public API.
What could improve#
Test coverage is uneven. The compiler’s SSA backend and runtime’s GC have fewer unit tests relative to their complexity — they rely on the integration test path (compiling real programs). Precise invariant testing of the GC write barrier, for instance, is difficult without internal exposure.
testing/quickis frozen. The property-based testing package (testing/quick) has been frozen since Go 1.0-era and is not accepting new features. The native fuzzer (testing.F) partially replaces it, butquick.Checkstill has no ergonomic successor for structured random testing of pure functions.No standard conformance testing pattern for goroutine leak detection. Unlike
testing/fstest’sTestFS, there is nogoroutine.CheckLeakin the stdlib. Individual packages implement ad-hoc goroutine leak detection using runtime stack introspection in cleanup hooks.
Patterns worth emulating#
- Script tests for CLI tools (
cmd/internal/script+ txtar) — any CLI tool with complex stateful behavior (file system, network, subprocess) should use this pattern instead of extensive unit-test mocking. export_test.gofor controlled white-box testing — a clean alternative to making internal types/functions public or relying on reflection.internal/testenv-style capability guards — centralizing platform and build-capability checks into named functions prevents test fragility from scattered GOOS/GOARCH conditionals.- Fuzz corpus as regression archive — treating fuzz-discovered inputs as committed test cases in
testdata/fuzz/gives the benefits of property-based testing with the reproducibility of snapshot tests. testing/synctest.Runfor deterministic concurrency tests (Go 1.24+) — avoidstime.Sleepin concurrent tests entirely by making goroutine scheduling and timers fully deterministic under a fake clock.