Add a Test
Purpose and Scope
This page explains the first testing workflow most Go developers need: add a package test, run it with the Go tool, and use the result to protect code as it changes. In Go, tests are ordinary Go code that live beside the package they check. The official tutorial frames the simplest path as creating a file whose name ends in _test.go, writing functions named TestName, accepting *testing.T, and reporting failures through methods on that value. That flow is intentionally lightweight: the test belongs to the package, imports the same dependencies as other Go files, and is executed by the go test command rather than by a separate test runner.
The repository evidence for this page comes from small toolchain and platform fixtures. These files are not an application tutorial, but they are useful because they show how testing-related code fits into real Go source: package declarations remain normal, imports can include testing, platform constraints can select files, cgo tests can refer to C symbols, and executable fixtures can validate expected values directly. Together, they reinforce the central rule for beginners: a Go test is part of a package workflow, not a special project format. You learn the convention once, then apply it to library packages, command packages, cgo packages, and low-level toolchain tests.
Sources: src/cmd/cgo/internal/test/issue24161e0/main.go, src/cmd/cgo/internal/test/issue24161e1/main.go, src/cmd/cgo/internal/test/issue24161e2/main.go, src/cmd/link/testdata/testBuildFortvOS/lib.go, src/cmd/link/testdata/testIndexMismatch/main.go, test/asmhdr.dir/main.go
Core Primitives
The first primitive is the package. A test normally declares the same package as the code under test when it needs direct access to unexported identifiers, or a package name ending in _test when it should exercise only the exported API. The cgo fixtures declare packages such as issue24161e0, issue24161e1, and issue24161e2; the linker fixtures declare package main; and the assembly-header fixture also uses package main. Those declarations show that testing is not tied to a separate namespace. The package remains the unit that the Go tool compiles, links, and checks.
The second primitive is the test function shape. In the tutorial, functions such as TestHelloName and TestHelloEmpty begin with Test, take t *testing.T, and call t.Errorf when the observed result differs from the wanted result. The cgo issue fixtures import testing and define a func Test(t *testing.T) {} placeholder. Even though those files are specialized fixtures, they show the key type-level contract: testing.T is the object a test receives for reporting failures, logging progress, skipping cases, and coordinating subtests or cleanup in fuller examples.
The third primitive is the Go command workflow. A beginner normally edits package code, creates greetings_test.go, and runs go test from the module or package directory. The command compiles the package and its tests, runs matching test functions, and reports success or failure. When a test fails, the important output is not merely that the command returned a non-zero status; it is the diagnostic written by the test. Good tests therefore include the input, the observed result, and the expected result in the failure message so the next edit is obvious.
Sources: src/cmd/cgo/internal/test/issue24161e0/main.go, src/cmd/cgo/internal/test/issue24161e1/main.go, src/cmd/cgo/internal/test/issue24161e2/main.go
Tutorial Flow
Start with a package that already builds. In the tutorial scenario, that package contains a function such as Hello that returns a message and an error. Add a new file beside the implementation named with the _test.go suffix, for example greetings_test.go. The suffix matters for the normal Go testing workflow because it marks the file as test source rather than production source. Put the same package declaration at the top if the test should exercise the package internally, then import testing and any helper packages needed to express expectations, such as regexp for matching text.
A minimal success-case test should arrange an input, call the function under test, and check the output. For a greeting function, that means choosing a name, calling Hello(name), and verifying that the returned message includes the name and that the error is nil. A minimal error-case test should call the same function with an invalid input and check that the failure is reported in the expected way. The official tutorial uses an empty string case and expects an empty message plus a non-nil error. Writing both cases early prevents the common mistake of only testing the happy path.
When you run go test, read failures as feedback about the contract you are defining. If the test says the message does not contain the supplied name, either the implementation is wrong or the test has described the wrong requirement. If the test says an expected error is missing, the package may be accepting invalid input silently. This is why Go examples usually name local variables want and compare them with actual results: the code reads like a compact specification. The repository’s test/asmhdr.dir/main.go fixture follows the same idea outside testing.T: it computes expected sizes and offsets, then prints mismatches when generated assembly-visible constants disagree.
package greetings
import (
"regexp"
"testing"
)
func TestHelloName(t *testing.T) {
name := "Gladys"
want := regexp.MustCompile(`\b` + name + `\b`)
msg, err := Hello(name)
if !want.MatchString(msg) || err != nil {
t.Errorf("Hello(%q) = %q, %v; want match for %v, nil", name, msg, err, want)
}
}
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("")
if msg != "" || err == nil {
t.Errorf("Hello(\"\") = %q, %v; want empty message and error", msg, err)
}
}Sources: test/asmhdr.dir/main.go
System-to-Code Mapping
The cgo issue fixtures show that tests in the Go tree often exist to preserve behavior around toolchain boundaries. Each of issue24161e0, issue24161e1, and issue24161e2 is guarded by //go:build darwin, imports C, supplies Objective-C cgo flags, links Apple frameworks, and calls Security framework symbols through generated C bindings. They also import testing and define a Test function. For a reader adding their first unit test, the lesson is not to copy the cgo code; it is to notice that testable Go code can still participate in platform-specific builds and foreign-function integration while keeping the same package and function conventions.
The linker fixtures broaden the picture to command packages and toolchain test data. src/cmd/link/testdata/testBuildFortvOS/lib.go is a package main file that imports C, exports GoFunc to C with //export, and has an empty main. src/cmd/link/testdata/testIndexMismatch/main.go is another package main fixture that imports package a and calls a.A() from main. These examples are small because their purpose is to be compiled or linked by higher-level tests. They are still normal Go packages, which is exactly why the Go test workflow scales from beginner code to the toolchain’s own regression fixtures.
The assembly-header fixture demonstrates assertion style without the testing package. It declares constants, variables populated from assembly-generated data, a struct whose size and field offsets matter, and a main function that compares each expected value with the observed value. When mismatches appear, it prints the name of the failing value and both sides of the comparison. In a unit test, those println checks would usually become t.Errorf calls. The underlying pattern is the same: name the property, compute the wanted value, compare it with the actual value, and emit enough information to diagnose the broken invariant.
Sources: src/cmd/cgo/internal/test/issue24161e0/main.go, src/cmd/cgo/internal/test/issue24161e1/main.go, src/cmd/cgo/internal/test/issue24161e2/main.go, src/cmd/link/testdata/testBuildFortvOS/lib.go, src/cmd/link/testdata/testIndexMismatch/main.go, test/asmhdr.dir/main.go
Relevant Source Files
src/cmd/cgo/internal/test/issue24161e0/main.go— Darwin cgo fixture that importstesting, defines aTestfunction, and exercises a CoreFoundation/Security symbol throughC.src/cmd/cgo/internal/test/issue24161e1/main.go— Similar cgo fixture that adds a Go import block withfmtandtesting, showing that tests can sit beside additional helper code.src/cmd/cgo/internal/test/issue24161e2/main.go— cgo fixture that declares a C reference variable and includes aTestfunction, grounding platform-specific package test cases.src/cmd/link/testdata/testBuildFortvOS/lib.go— Linker test data for a cgo-enabledpackage mainwith an exported Go function and amainentry point.src/cmd/link/testdata/testIndexMismatch/main.go— Minimal command package fixture that imports another package and calls it frommain, illustrating package-level build inputs used by tests.test/asmhdr.dir/main.go— Runtime-style validation program that compares constants, strings, sizes, and offsets against expected values, mirroring the arrange/check/report structure of tests.
Implementation Details and Good Practices
Keep each test focused on one observable behavior. A test named TestHelloName should make it clear that the successful name case is under review; a test named TestHelloEmpty should make it clear that invalid input is under review. Avoid names such as TestAll for beginner code because they hide the failure location and encourage unrelated checks to accumulate in one function. The cgo fixtures use the very small name Test because they are specialized regression fixtures, but ordinary package tests should use descriptive names that communicate the behavior being protected.
Prefer failure messages that include the function call, the actual result, and the wanted result. The tutorial’s t.Errorf examples are effective because they show the input, returned values, and expectation in one line. This matters more as a package grows: a failure report should let another developer understand the broken contract without reading the entire test first. In low-level fixtures such as test/asmhdr.dir/main.go, printed labels like typSize, typA, stringVal, and longStringVal serve the same diagnostic role by naming the invariant that failed.
Run tests as part of the edit loop, not only at the end. The usual sequence is: implement a small behavior, add or adjust a test, run go test, fix the implementation or the expectation, and repeat. When a package uses build constraints or cgo, also remember that the test surface may depend on the selected platform and toolchain configuration. The Darwin-only cgo fixtures demonstrate that some test inputs are intentionally compiled only where their build tags and external frameworks make sense. For everyday module code, this usually means keeping platform-specific tests explicit and naming files or build constraints carefully.
Next Steps
After you can add one test, extend the same pattern to tables, subtests, examples, and benchmarks. A table-driven test stores several inputs and expected results in a slice, then loops over them with t.Run so each case is named. Examples can double as executable documentation when they have expected output comments. Benchmarks use the testing.B type to measure repeated operations. The important foundation remains unchanged: tests are Go code, packages are the unit of organization, go test is the command that runs the workflow, and clear failure messages are the fastest path from a broken change to a correct fix.
Related pages: getting-started, writing-go-code, tutorial-create-module, testing