Skip to main content
eLearner.app
Module 9 · Lesson 1 of 541/50 in the course~12 min
Module lessons (1/5)

The `testing` package

In Go, tests are part of the stdlib: no external library, no framework to configure. All you need is a *_test.go file next to the code, a few TestXxx functions, and the go test command.

Basic conventions

  • Test files are named <something>_test.go and live in the same package as the code they test (or in a <name>_test package for "black-box tests").
  • Test functions have an exact signature: func TestXxx(t *testing.T) where Xxx starts with an uppercase letter.
  • You import testing from the stdlib.
  • No assertEquals: you use if got != want { t.Errorf(...) } with fmt-verbs.
Go
// math.go
package math

func Sum(a, b int) int { return a + b }

// math_test.go
package math

import "testing"

func TestSum(t *testing.T) {
    got := Sum(2, 3)
    if got != 5 {
        t.Errorf("Sum(2,3) = %d; voglio %d", got, 5)
    }
}

Run it with:

Bash
go test ./...        # all packages in the module
go test -v ./pkg     # verbose: prints test names
go test -run TestSum  # only tests matching the regex

t.Error vs t.Fatal

These are the two basic verbs to signal a failure:

MethodEffect
t.Log(args...)Logs but does not fail (visible only with -v or on failure).
t.Error(args...)Logs, marks FAIL, continues the test function.
t.Errorf(fmt, ...)Like Error with formatting.
t.Fatal(args...)Logs, marks FAIL, stops the test (calls runtime.Goexit).
t.Fatalf(fmt, ...)Like Fatal with formatting.

Rule of thumb: use Fatal when continuing makes no sense (e.g. failed setup, imminent nil pointer dereference); otherwise Error, which lets you see all failures in a single run.

Go
func TestOpen(t *testing.T) {
    f, err := os.Open("fixtures/sample.txt")
    if err != nil {
        t.Fatal(err) // if it doesn't open, the next steps would fail on nil
    }
    defer f.Close()
    // ... assert on content with t.Errorf
}

Setup and teardown with t.Cleanup

To release resources at the end of a test (or sub-test), use t.Cleanup instead of defer:

Go
func TestDB(t *testing.T) {
    db := newTestDB(t)
    t.Cleanup(func() { db.Close() })
    // ... test
}

The advantage over defer: the helper that creates db can register the cleanup internally, so callers don't need to remember it.

Test helpers

The t.Helper() method marks a function as a "helper": when a test fails, the stack trace skips the helper function and points directly to the caller:

Go
func mustParseInt(t *testing.T, s string) int {
    t.Helper()
    n, err := strconv.Atoi(s)
    if err != nil {
        t.Fatalf("parse %q: %v", s, err)
    }
    return n
}

Exercises

Exercise#go.m9.l1.e1
Attempts: 0Loading…

Define the TestSum function that verifies Sum(2,3) == 5 using t.Errorf with a formatted message.

Loading editor…

Solution available after 3 attempts

Exercise#go.m9.l1.e2
Attempts: 0Loading…

In TestDiv, use t.Fatal to immediately stop the test if Div returns an unexpected value.

Loading editor…

Solution available after 3 attempts

Quiz#go.m9.l1.e3
Ready

What is the fundamental difference between t.Error and t.Fatal?

Go
if err != nil {
  t.???(err)
}
Answer options