Subtests #
Golang encourages using subtests.
package main
import "testing"
func Hello(name string) string {
if name == "" {
return "Hello, World"
}
return "Hello, " + name
}
func TestHello(t *testing.T) {
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run(
"say 'Hello, World' when an empty string is supplied",
func(t *testing.T) {
got := Hello("")
want := "Hello, World"
assertCorrectMessage(t, got, want)
},
)
}
func assertCorrectMessage(t testing.TB, got, want string) {
t.Helper()
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
=== RUN Test_hello
=== RUN Test_hello/saying_hello_to_people
=== RUN Test_hello/say_'Hello,_World'_when_an_empty_string_is_supplied
--- PASS: Test_hello (0.00s)
--- PASS: Test_hello/saying_hello_to_people (0.00s)
--- PASS: Test_hello/say_'Hello,_World'_when_an_empty_string_is_supplied (0.00s)
PASS
Testing interfaces #
Golang has a bunch of testing interfaces one can implement depending on what one wants to accomplish. Here are a few examples:
General tests #
package main
import "testing"
func Hello() string {
return "Hello, world"
}
func TestHello(t *testing.T) {
got := Hello()
want := "Hello, world"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
=== RUN TestHello
--- PASS: TestHello (0.00s)
PASS
Fuzzing #
Fuzzing is supported out of the box.
package main
import (
"testing"
"unicode/utf8"
)
func Reverse(s string) string {
b := []byte(s)
for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return string(b)
}
func FuzzReverse(f *testing.F) {
testcases := []string{"Hello, world", " ", "!12345"}
for _, tc := range testcases {
f.Add(tc) // Use f.Add to provide a seed corpus
}
f.Fuzz(func(t *testing.T, orig string) {
rev := Reverse(orig)
doubleRev := Reverse(rev)
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}
=== RUN FuzzReverse
=== RUN FuzzReverse/seed#0
=== RUN FuzzReverse/seed#1
=== RUN FuzzReverse/seed#2
--- PASS: FuzzReverse (0.00s)
--- PASS: FuzzReverse/seed#0 (0.00s)
--- PASS: FuzzReverse/seed#1 (0.00s)
--- PASS: FuzzReverse/seed#2 (0.00s)
PASS
Note that fuzz tests must include the Fuzzz
prefix.
Benchmarking #
Benchmarks are recognized by the Benchmark
prefix in tests
package main
import "testing"
func Repeat(character string) (repeated string) {
for i := 0; i < 5; i++ {
repeated += character
}
return
}
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
The framework determines a “good” value for b.N
and runs the benchmark accordingly. To run benchmarks do
shell go test -bench=.
. Then you should get something like the following output:
goos: linux
goarch: amd64
pkg: hello/iteration
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkRepeat-16 15913407 113.3 ns/op
PASS
ok hello/benchmark 1.882s
t.Helper #
t.Helper()
is needed when writing helper methods. By doing this, when it fails, the line number reported will be in
the function call rather than inside the test helper.
package main
import "testing"
func Hello(name string) string {
return "Hello, " + name
}
func TestHello(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
}
func assertCorrectMessage(t testing.TB, got, want string) {
t.Helper()
t.Errorf("got %q want %q", got, want)
}
=== RUN TestHello
prog_test.go:12: got "Hello, Chris" want "Hello, Chris"
--- FAIL: TestHello (0.00s)
FAIL
Testable examples #
Testable examples are snippets of Go code that are displayed as package documentation and that are verified by running them as tests. They can also be run by a user visiting the godoc web page for the package and clicking the associated “Run” button.
package main
import "fmt"
func Add(x, y int) int {
return x + y
}
func ExampleAdd() {
sum := Add(1, 5)
fmt.Println(sum)
// Output: 6
}
=== RUN ExampleAdd
--- PASS: ExampleAdd (0.00s)
PASS
If you omit // Output: 6
the test example will not run anymore, however it will still be compiled.
Race detector #
Golang comes with a handy race detector to make your life easier when running concurrent code.
go test -race
Mocking #
httptest #
Golang includes the net/http/httptest package to mock http servers in the standard library, this is very helpful when writing tests.
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func GetStatusCode(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
return resp.Status, nil
}
func TestGetStatusCode(t *testing.T) {
mockServer := httptest.NewServer(
http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
},
),
)
defer mockServer.Close()
want := "200 OK"
got, _ := GetStatusCode(mockServer.URL)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
=== RUN TestGetStatusCode
--- PASS: TestGetStatusCode (0.00s)
PASS
fstest #
Go provides the testing/fstest package to mock filesystems
package main
import (
"io/fs"
"testing"
"testing/fstest"
)
func GetFileContent(fileSystem fs.FS, filename string) (string, error) {
body, err := fs.ReadFile(fileSystem, filename)
if err != nil {
return "", err
}
return string(body), nil
}
func TestGetFileContent(t *testing.T) {
testFs := fstest.MapFS{
"file.md": {Data: []byte("body")},
}
got, err := GetFileContent(testFs, "file.md")
if err != nil {
t.Fatal(err)
}
want := "body"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
=== RUN TestGetFileContent
--- PASS: TestGetFileContent (0.00s)
PASS
A comprehensive write-up on fstest
has been written by Ben Congdon.
Property tests #
Go allows for property tests via the built-in testing/quick library. This allows you to test your code against random inputs
package main
import (
"strings"
"testing"
"testing/quick"
)
type RomanNumeral struct {
Value uint16
Symbol string
}
var allRomanNumerals = []RomanNumeral{
{1000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
}
func ConvertToRoman(arabic uint16) string {
var result strings.Builder
for _, numeral := range allRomanNumerals {
for arabic >= numeral.Value {
result.WriteString(numeral.Symbol)
arabic -= numeral.Value
}
}
return result.String()
}
func ConvertToArabic(roman string) uint16 {
var arabic uint16 = 0
for _, numeral := range allRomanNumerals {
for strings.HasPrefix(roman, numeral.Symbol) {
arabic += numeral.Value
roman = strings.TrimPrefix(roman, numeral.Symbol)
}
}
return arabic
}
func TestPropertiesOfConversion(t *testing.T) {
assertion := func(arabic uint16) bool {
t.Log("testing", arabic)
roman := ConvertToRoman(arabic)
fromRoman := ConvertToArabic(roman)
return fromRoman == arabic
}
if err := quick.Check(assertion, nil); err != nil {
t.Error("failed checks", err)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, playground")
}
=== RUN TestPropertiesOfConversion
prog_test.go:59: testing 8204
prog_test.go:59: testing 61907
prog_test.go:59: testing 51089
prog_test.go:59: testing 2705
prog_test.go:59: testing 62112
prog_test.go:59: testing 31345
prog_test.go:59: testing 25291
prog_test.go:59: testing 12293
prog_test.go:59: testing 25891
prog_test.go:59: testing 42147
prog_test.go:59: testing 39197
prog_test.go:59: testing 51142
prog_test.go:59: testing 24486
prog_test.go:59: testing 32988
prog_test.go:59: testing 2923
prog_test.go:59: testing 15449
prog_test.go:59: testing 14345
prog_test.go:59: testing 11215
prog_test.go:59: testing 9569
prog_test.go:59: testing 59569
prog_test.go:59: testing 48040
prog_test.go:59: testing 59436
prog_test.go:59: testing 26845
prog_test.go:59: testing 34798
prog_test.go:59: testing 48536
prog_test.go:59: testing 43567
prog_test.go:59: testing 37603
prog_test.go:59: testing 33086
prog_test.go:59: testing 13605
prog_test.go:59: testing 7777
prog_test.go:59: testing 33550
prog_test.go:59: testing 5594
prog_test.go:59: testing 41645
prog_test.go:59: testing 14687
prog_test.go:59: testing 33943
prog_test.go:59: testing 40170
prog_test.go:59: testing 65273
prog_test.go:59: testing 42607
prog_test.go:59: testing 53963
prog_test.go:59: testing 11525
prog_test.go:59: testing 58320
prog_test.go:59: testing 41298
prog_test.go:59: testing 21953
prog_test.go:59: testing 1199
prog_test.go:59: testing 41238
prog_test.go:59: testing 58843
prog_test.go:59: testing 7721
prog_test.go:59: testing 27822
prog_test.go:59: testing 6873
prog_test.go:59: testing 11785
prog_test.go:59: testing 31488
prog_test.go:59: testing 9017
prog_test.go:59: testing 51631
prog_test.go:59: testing 31214
prog_test.go:59: testing 57380
prog_test.go:59: testing 60907
prog_test.go:59: testing 26398
prog_test.go:59: testing 6907
prog_test.go:59: testing 39703
prog_test.go:59: testing 1989
prog_test.go:59: testing 42865
prog_test.go:59: testing 32234
prog_test.go:59: testing 6222
prog_test.go:59: testing 53520
prog_test.go:59: testing 40739
prog_test.go:59: testing 1818
prog_test.go:59: testing 37117
prog_test.go:59: testing 14014
prog_test.go:59: testing 54324
prog_test.go:59: testing 36223
prog_test.go:59: testing 13444
prog_test.go:59: testing 51355
prog_test.go:59: testing 18286
prog_test.go:59: testing 288
prog_test.go:59: testing 54756
prog_test.go:59: testing 22306
prog_test.go:59: testing 17684
prog_test.go:59: testing 56489
prog_test.go:59: testing 25530
prog_test.go:59: testing 37525
prog_test.go:59: testing 39480
prog_test.go:59: testing 57702
prog_test.go:59: testing 57988
prog_test.go:59: testing 28377
prog_test.go:59: testing 53868
prog_test.go:59: testing 28457
prog_test.go:59: testing 38090
prog_test.go:59: testing 6190
prog_test.go:59: testing 24643
prog_test.go:59: testing 50892
prog_test.go:59: testing 16835
prog_test.go:59: testing 527
prog_test.go:59: testing 64266
prog_test.go:59: testing 40598
prog_test.go:59: testing 60101
prog_test.go:59: testing 5102
prog_test.go:59: testing 14169
prog_test.go:59: testing 53192
prog_test.go:59: testing 23969
prog_test.go:59: testing 12735
--- PASS: TestPropertiesOfConversion (0.00s)
PASS
short flag #
Go provides a short flag to skip tests that take a long time
(i.e., acceptance tests) if the -short
argument is passed when testing.
When running go test -short ./...
the following test is skipped
package main
import "testing"
func Add(x, y int) int {
return x + y
}
func TestAdd(t *testing.T) {
// This test will be skipped if -short is passed
if testing.Short() {
t.Skip()
}
want := 5
got := Add(3, 2)
if got != want {
t.Errorf("got %v want %v", got, want)
}
}
context #
When you need a context but don’t want to mock out the entire request lifecycle, context.Background() can be used to provide a context for test functions.
t.Parallel #
t.Parallel()
can be used when you want tests in a single package to run in parallel.
First the sequential tests without t.Parallel()
are run, then the parallel tests.
package main
import "testing"
func returnSomething() string {
return "something"
}
func Test_returnSomething(t *testing.T) {
t.Run("test a is run in parallel", func(t *testing.T) {
t.Parallel()
got := returnSomething()
want := "something"
testHelper(t, got, want)
})
t.Run("test b is not run in parallel", func(t *testing.T) {
got := returnSomething()
want := "something"
testHelper(t, got, want)
})
t.Run("test c is run in parallel", func(t *testing.T) {
t.Parallel()
got := returnSomething()
want := "something"
testHelper(t, got, want)
})
}
func testHelper(t *testing.T, got string, want string) {
t.Helper()
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
=== RUN Test_returnSomething
=== RUN Test_returnSomething/test_a_is_run_in_parallel
=== PAUSE Test_returnSomething/test_a_is_run_in_parallel
=== RUN Test_returnSomething/test_b_is_not_run_in_parallel
=== RUN Test_returnSomething/test_c_is_run_in_parallel
=== PAUSE Test_returnSomething/test_c_is_run_in_parallel
=== CONT Test_returnSomething/test_a_is_run_in_parallel
=== CONT Test_returnSomething/test_c_is_run_in_parallel
--- PASS: Test_returnSomething (0.00s)
--- PASS: Test_returnSomething/test_b_is_not_run_in_parallel (0.00s)
--- PASS: Test_returnSomething/test_a_is_run_in_parallel (0.00s)
--- PASS: Test_returnSomething/test_c_is_run_in_parallel (0.00s)
PASS