Fundamentals
Go includes built-in testing tools for functional and performance testing.
- Unit testing
- Checks code in individual functions or modules.
- Integration testing
- Verifies that different modules work well together.
- Functional testing
- Verifies the correctness of the program output.
- Test suite
- Collection of test cases. If you use testing resources, set up the resources at the start of the test suite, then tear them down at the end.
Conventions
All Go tests must follow these conventions:
- All test files must end in
_test.go, which lets the Go test tool identify them. - Each test function must start with
TestXxx, with the remainder of the name in camel case. - A test function takes a single argument, a pointer to
testing.T.Tis a struct with testing methods that manages test state and can log test results.
File location
Place test files in the same package as the code that they test.
project-root
├── go.mod
├── main.go
└── packagename
├── functional.go # source code
└── testing_test.go # test file
External tests
For external (integration) tests, use the original package name followed by _test. For example:
package original_test
This package format requires that you import the source into the test file.
Writing tests
Each test contain three main phases:
- Arrange: Set up the test inputs and expected values:
a := 2, b := 3 - Act: Execute the code that you are testing:
got := Add(a, b), want := 5 - Assert: Verify that the code returns the correct values. You can use the
got/wantorgot/expectedsemantics:if got != want { ... }
| Phase | Purpose | Example |
|---|---|---|
| Arrange | Set up the test inputs and expected values. | a := 2, b := 3 |
| Act | Execute the code that you are testing. | got := Add(a, b), want := 5 |
| Assert | Verify that the code returns the correct values. You can use the got/want or got/expected semantics. | if got != want { ... } |
Single test
Run a single test to validate one specific behavior of a function. The three phases are shown in comments because single tests are usually simple:
func TestParse(t *testing.T) {
const uri = "https://github.com/username" // 1. Arrange
got, err := Parse(uri) // 2. Act
if err != nil {
t.Fatalf("Parse (%q) err = %q, want <nil>", uri, err)
}
want := &URL{
Scheme: "https",
Host: "github.com",
Path: "username",
}
if *got != *want { // 3. Assert
t.Errorf("Parse (%q)\ngot %#v\nwant %#v", uri, got, want)
}
}
Table test
Table tests separate the test data from the logic so you can reuse the logic for different test cases. Use a table test when you need to test multiple inputs.
Arrange
A common way to arrange a test is to use table tests. Table tests are a way to provide multiple test cases that you loop over and test during the Act stage. To set up a table test, complete the following:
- Create a
testCasestruct that models the inputs and expected outputs of the test:type testCase struct { a int b int expected int }namefieldTable tests commonly use a
namefield so you can distinguish between tests. For more information, see Unit testing: Subtests. - Use a map literal with a
stringkey andtestCasevalue. Thestringkey is the name of the test, andtestCaseis the test values:tt := map[string]testCase{ // tt for table tests "test one": { a: 4, b: 5, expected: 9, }, "test two": { a: -4, b: 15, expected: 11, }, "test three": { a: 5, b: 1, expected: 6, }, }
Act
Within the same TestAdd() function, write a for range loop. This is where you execute each test case with the code that you are testing. Use the t.Run() subtest method in the for range loop to run each individual test case with a name. t.Run() accepts two parameters: the name of the test, and an unnamed test function:
for name, tc := range tt {
t.Run(name, func(t *testing.T) {
// act
got := add(tc.a, tc.b)
...
})
}
In the previous example, name is the key in the tt map, and tc is the testCase struct in the tt map.
Assert
In the assert step, you compare the actual values (what you got in the Act step) with the expected value, which is usually a field in the testCase struct. Asserts are generally if statements that return a formatted error with t.Errorf when the got and expected values do not match:
for name, tc := range tt {
t.Run(name, func(t *testing.T) {
...
// assert
if got != tc.expected {
t.Errorf("expected %d, got %d", tc.expected, got)
}
})
}
Assert helper
Writing assert logic can get tedious, so you should extract it into a helper function. Add the following code to /internal/assert/assert.go:
package assert
import (
"testing"
)
func Equal[T comparable](t *testing.T, got, expected T) {
t.Helper()
if got != expected {
t.Errorf("got: %v; want: %v", got, expected)
}
}
The preceding example uses generics.
Now, you can use this helper function to verify test output during the assert stage:
for name, tc := range tt {
t.Run(name, func(t *testing.T) {
...
// assert
assert.Equal(t, got, tc.expected)
})
}
Test commands
- Verbose output.
- Test results are cached, but this skips the cache and forces Go to rerun the test.
- Add the
-shuffle=onflag to execute tests in a random order. This command returns the “shuffling seed” number. You can use this to run tests in the same order. This is helpful if you have a bug and want to reproduce the tests until you fix the issue:go test -v -shuffle=on -test.shuffle 1769368631148682425 ... - Stops tests in a single package if there is a failing test. This is helpful if you want to work on the first failing test.
- Run a specific test.
- Run a specific subtest.
- Globbing syntax. This test runs a specific subtest that begins with
with_port. - Use the short flag to skip long-running tests, like integration tests. The test function must use the
testing.Short()function, and optionally uset.Skipto provide context for skipping the test.
go test -v // 1
go test -count=1 // 2
go test -v -shuffle=on // 3
go test -v -shuffle=1769368631148682425
go test -v -failfast // 4
go test -v -run=TestName // 5
go test -v -run=TestName/with_port // 6
go test -v -run=TestName/^with_port // 7
go test -v -short ./... // 8
Example tests
Writing example tests within your test code shows how to use your package. If you change your code, the documentation updates automatically.
A testable example is live documentation for code. You write a testable example to demonstrate the package API to other developers. The API includes the exported identifiers, such as functions, methods, etc. A testable example never goes out of date.
The testing package runs testable examples and checks their results, but it does not report successes or failures.
Conventionally, testable example files are named example_test.go. If you create multiple files, use the _test.go suffix. These examples display alongside the corresponding package in the documentation:
The
_testsuffix declares a test-only package that is named after the package that it tests. This is called blackbox testing—it can access only the package that it tests.Go usually doesn’t allow multiple packages in the same directory, but it makes an exception for test packages. The
_testsuffix means that you cannot import it from other packages.You have to import the package that you are testing.
Name the test
Example<FuncToTest>. Example tests print their output to stdout, so they do not take a*Ttype.Demonstrate how to use the function you are testing.
Print the result of the test to stdout. This is critical for the output assertion in the next step.
Output assertion. The
// Outputcomment signals that the following line should equal what was printed to stdout.This line match stdout. Whitespace and newlines matter.
package urlcopy_test // 1
import (
"fmt"
"log"
"urlcopy" // 2
)
func ExampleParse() { // 3
uri, err := urlcopy.Parse("https://github.com/username") // 4
if err != nil {
log.Fatal(err)
}
fmt.Println(uri) // 5
// Output: // 6
// https://github.com/username // 7
}
Naming conventions
Testable examples use the following naming conventions:
| Signature | Description |
|---|---|
func Example() | Example for the entire package. |
func ExampleParse() | Example for the Parse function. |
func ExampleURL() | Example for the URL type. |
func ExampleURL_Hostname() | Example for the Hostname method on the URL type. |
godoc server
You can generate docs that include your testable examples with godoc. The following command installs the latest version:
$ go install golang.org/x/tools/cmd/godoc@latest
To view any ExampleXxx functions as Go documentation, run the go doc server with the following command:
$ godoc -play -http ":6060"
To show additional examples of the same type, use the _xxx() suffix on the function name. For example:
func ExampleURL(){...}
func ExampleURL_fields(){...}
Comparing structs
Comparing structs is not as straightforward as primitive types. You can use the reflect package, but the go-cmp package is easier. Run this command to download the package:
go get github.com/google/go-cmp/cmp
Here is an example of how to compare values:
func TestParseWithoutPath(t *testing.T) {
const uri = "https://github.com"
got, err := Parse(uri)
if err != nil {
t.Fatalf("Parse (%q) err = %q, want <nil>", uri, err)
}
want := &URL{
Scheme: "https",
Host: "github.com",
Path: "",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("Parse(%q) mismatch (-want +got):\n%s", uri, diff)
}
}
Here is the sample output:
go test -v .
=== RUN TestParseWithoutPath
url_test.go:45: Parse("https://github.com") mismatch (-want +got):
&urlcopy.URL{
Scheme: "https",
Host: "github.com",
- Path: "",
+ Path: "username",
}
...
Failure messages
Test failure messages should be easy to read and show you how the test failed. Use \n and the correct format verbs:
Formatting verbs
If you use %s, Errorf calls the type’s String method. Instead, use %#v to show exactly how the value is represented in code. Here are all versions of this format verb:
%v: Default value format%+v: Include struct field names%#v: Go-syntax representation
got, err := Parse(uri)
if err != nil {
t.Fatalf("Parse (%q) err = %q, want <nil>", uri, err)
}
if *got != *want {
t.Errorf("Parse (%q)\ngot %#v\nwant %#v", uri, got, want)
}
Log, Error, Fatal
- t.Log
- You want to debug or show output but do not want to fail the test.
- t.Error
- Something is wrong but the test can still keep running to collect more failures.
- t.Fatal
- There’s no point continuing the test. For example, bad input, required init failed.
| Function | Description | Use Case |
|---|---|---|
t.Log | Prints debugging or informational output. | Show internal state or progress without failing the test. |
t.Logf | Prints formatted log output. | Outputs formatted debugging output. |
t.Error | Logs an error and marks the test as failed. | Something is wrong but the test can continue running. |
t.Errorf | Logs a formatted error message and marks the test as failed. | Need a detailed or formatted error message. |
t.Fail | Marks the test as failed without printing a message. | Use in helper functions to mark failure without logging. |
t.FailNow | Marks test as failed and immediately stops execution. | When continuing the test is pointless (e.g., missing required test setup). |
t.Fatal | Logs a fatal error and stops the test immediately. | When a critical issue prevents further execution. |
t.Fatalf | Same as t.Fatal but with formatting support. | Outputs formatted fatal error messages. |