Skip to main content

Testing in Go

Introduction​

The Go standard library provides a testing framework with all the tools required to write robust unit tests.

You can read the documentation for the testing package here, but, as you'll see, unit testing in Go follows the language's guiding principle: simple.


Concepts​

Key rules and conventions​

  • Test files must be named with a _test.go suffix
  • Test functions must start with Test (case matters)
  • Test functions must have the signature func TestXxx(t *testing.T)
  • By convention, tests are placed beside the code they test, e.g. in the example above, calc.go and calc_test.go would be in the same directory.

Table-driven testing​

It's common to write Go unit tests with a table-driven approach. Here, a test provides multiple values to test along with expected results. It's likely that if you ask Copilot to generate tests, it will create tests using this approach. See below for an example.

t.Error, t.Fail, and t.Fatal​

  • t.Fail() marks the test as failed, but does not log a message.
  • t.Error(...) logs a message and marks the test as failed.
  • t.Fatal(...) logs a message, marks the test as failed, and stops the current test immediately.

Use t.Error when a test can continue after a failed check, and t.Fatal when continuing would make the rest of the test invalid.

Examples​

calc.go
package calc

// Add is a simple function adding its two arguments
// and returning the result.
func Add(number1, number2 int) int {
return number1 + number2
}
calc_test.go
package calc

import (
"testing"
)

// Remember to name your test functions with TestXxx
func TestAddSimple(t *testing.T) {
got := Add(1, 2)
wanted := 3
if got != wanted {
t.Error("1 + 2 should equal 3")
}
}

func TestAddTableDriven(t *testing.T) {
tests := []struct {
name string
first, second int
wanted int
}{
{"positive numbers", 2, 3, 5},
{"zeros", 0, 0, 0},
{"negative numbers", -2, -3, -5},
{"mixed signs", -2, 3, 1},
{"large numbers", 1000000, 2000000, 3000000},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.first, tt.second)
if got != tt.wanted {
t.Errorf("Add(%d, %d) = %d, wanted %d", tt.first, tt.second, got, tt.wanted)
}
})
}
}

func helperFunction() {
// helper functions are fine, but only TestXxx functions are executed by go test
}

Then run the go test command in a terminal.

$ go test ./...
ok tests 0.032s

go test ./... will find all test files in the current and nested directories, then run all test functions that it finds.

Challenges​

1. Write a simple test for a function that Copilot creates​

  • Create a new package called spaces
  • Add a new file called spaces.go and declare package spaces at the top of the file
  • Open Copilot Chat and ask: Write a function in Go that counts the spaces in a string. Call the function CountSpaces. It will take a string argument and return an integer result.
  • Write a unit test for the new function that tests a single string argument
  • Run go test and check the output
💡 Show Answer
spaces_test.go
package spaces

import (
"testing"
)

func TestCountSpaces(t *testing.T) {
got := CountSpaces("there should be 4 spaces")
wanted := 4
if got != wanted {
t.Error("expected there to be 4 spaces")
}
}
go test ./...

2. Ask Copilot to write a table-driven test for the CountSpaces function​

  • Open Copilot Chat and ask: Add a table-driven test for the CountSpaces function and call it TestCountSpacesTableDriven.
  • Run go test and check the output
💡 Show Answer

Copilot will suggest something like the following:

func TestCountSpacesTableDriven(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{"empty string", "", 0},
{"no spaces", "hello", 0},
{"single space", "hello world", 1},
{"multiple spaces", "one two three", 2},
{"only spaces", " ", 3},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CountSpaces(tt.input)
if got != tt.want {
t.Errorf("CountSpaces(%q) = %d, want %d", tt.input, got, tt.want)
}
})
}
}

3. Add some scenarios to the table-driven test​

  • Add the following scenarios to the tests slice
  • Check that the function counts a leading space
  • Check that the function counts a trailing space
  • Check that tabs are not counted
  • Run go test and check the output
💡 Show Answer

Add the following entries to the tests slice:

{"leading space", " hello", 1},
{"trailing space", "hello ", 1},
{"tabs not counted", "hello\tworld", 0},

4. Compare t.Fail, t.Error, and t.Fatal​

  • Ask Copilot: Show me the difference between t.Fail, t.Error, and t.Fatal in one test file with examples.
  • Add three tests named TestWithFail, TestWithError, and TestWithFatal.
  • Run go test -v and observe which log lines appear for each test.
💡 Show Answer
failure_methods_test.go
package spaces

import "testing"

func TestWithFail(t *testing.T) {
t.Log("before t.Fail")
t.Fail()
t.Log("after t.Fail") // still runs
}

func TestWithError(t *testing.T) {
t.Log("before t.Error")
t.Error("something was wrong")
t.Log("after t.Error") // still runs
}

func TestWithFatal(t *testing.T) {
t.Log("before t.Fatal")
t.Fatal("cannot continue")
t.Log("after t.Fatal") // unreachable
}

t.Fail and t.Error both allow the current test to continue. t.Fatal stops the current test function immediately.


5. Measure coverage and identify missing test cases​

  • Run go test -cover ./... and note the coverage percentage.
  • Ask Copilot: Based on my CountSpaces tests, what edge cases might still be missing?
  • Add at least one new case that increases confidence, then run coverage again.
💡 Show Answer
go test -cover ./...

You can also generate a detailed report:

go test -coverprofile=cover.out ./...
go tool cover -func=cover.out

Possible extra test cases:

{"multiple consecutive spaces", "a b c", 5},
{"space with punctuation", "hello, world !", 2},

6. Stretch: add a benchmark for CountSpaces​

  • Ask Copilot: Create a Go benchmark called BenchmarkCountSpaces for my CountSpaces function.
  • Run go test -bench=. ./....
  • Increase the input string size and compare benchmark results.
💡 Show Answer
spaces_benchmark_test.go
package spaces

import (
"strings"
"testing"
)

func BenchmarkCountSpaces(b *testing.B) {
input := strings.Repeat("word ", 1000)
b.ResetTimer()

for i := 0; i < b.N; i++ {
_ = CountSpaces(input)
}
}
go test -bench=. ./...

7. Stretch: add a fuzz test​

  • Ask Copilot: Create a fuzz test for CountSpaces called FuzzCountSpaces.
  • Seed it with a few initial strings.
  • Run go test -fuzz=Fuzz -run=^$ and let it execute for a short period.
💡 Show Answer
spaces_fuzz_test.go
package spaces

import (
"strings"
"testing"
)

func FuzzCountSpaces(f *testing.F) {
seeds := []string{"", "hello", "hello world", " leading and trailing ", "tabs\tand spaces "}
for _, s := range seeds {
f.Add(s)
}

f.Fuzz(func(t *testing.T, input string) {
got := CountSpaces(input)
want := strings.Count(input, " ")
if got != want {
t.Fatalf("CountSpaces(%q) = %d, want %d", input, got, want)
}
})
}
go test -fuzz=Fuzz -run=^$