Golang std testing

From wikinotes

Go ships with a minimalist test suite.

Documentation

testing https://pkg.go.dev/testing@go1.18.3

Usage

go test                              # run all tests
go test ./...                        # run all tests
go test -run FooTest                 # run top-level tests containing 'FooTest'
go test -run FooTest ./internal/foo  # run tests matching 'FooTest in package at '/internal/foo'
go test ./internal/foo -v            # run internal/foo tests, and print

you can also create an ad-hoc test-package and run exclusively a ad-hoc test.go package,
but this is not considered the norm

go test internal/foo/bar_test.go

Example

The builtin go test framework is fairly minimalist.
Tests are just functions, you can loop them if useful.

Tests are typically kept alongside code.

// myproject/mypackage/mylib.go

package mypackage

func Hello(name string) string {
    return "Hello, " + name
}
// myproject/mypackage/mylib_test.go

package mypackage

import "testing"

func TestHello(t *testing.T) {
    res := Hello("Adam")
    if res != "Hello, Adam" {
        t.Errorf("Hello() result did not match")
    }
}

Sample Strategies

1x test per struct

The mangos library uses a really clean approach.
A library of assertions is created, then each test

  • Instantiates a struct
  • Asserts on all public methods of the struct
https://github.com/nanomsg/mangos/blob/master/test/certs_test.go test cases (per struct)
https://github.com/nanomsg/mangos/blob/master/test/util.go test assertions

Table Tests

See subtests below.

func TestHello(t *testing.T) {
    tcases := []struct {                         // <-- slice of structs containing testdata
        test       string
        name       string
        expects    string
    }{
        { test: "ValidName", name: "Adam", },
        { test: "NilName", name: nil, },
        { test: "InvalidName", name: nil, },
    }

    for _, tcase := range tcases {
        t.Run(tcase.test, func(t *testing.T) {   // <-- t.Run() evaluates each case
            if res := hello.Hello(tcase.name); res != tcase.expects {
                t.Errorf("Failed because...")
            }
        }
    }
}

Assertions

There are no assertions, you are responsible for tests and messages.

func TestHello(t *testing.T) {
    // log message and fail (but continue executing)
    t.Errorf("expected: %v\nreceived: %v", expects, received)
    t.Fail()         // mark test as failed, but continue
    t.FailNow()      // mark test as failed and stop executing

    t.Skip("Reason") // log, and stop executing

    t.TempDir()      // provides a tempdir that is deleted once test finishes running
}

Subtests

Evaluate subtests under one function using t.Run("TESTNAME", ...).

These can be expressed as Table Tests:

import "example.com/x/hello"                     // <-- despite sharing package, must import package in test

func TestHello(t *testing.T) {
    tcases := []struct {                         // <-- slice of structs containing testdata
        test       string
        name       string
        expects    string
    }{ { test: "ValidName", name: "Adam", },
       { test: "NilName", name: nil, } }

    for _, tcase := range tcases {
        t.Run(tcase.test, func(t *testing.T) {   // <-- t.Run() evaluates each case
            res := hello.Hello(tcase.name)
            if res != tcase.expects {
                t.Errorf("Failed because...")
            }
        }
    }
}

Or you can define simply unique subtests

import "github.com/stretchr/testify/assert"
import "regexp"
import "testing"

func TestHello(t *testing.T) {
    t.Run("Begins with H", func(t *testing.T) {
        res := hello.Hello("Alex")
        assert.Regexp(t, regexp.MustCompile("^h"), res)
    }

    t.Run("Last word is name", func(t *testing.T) {
        res := hello.Hello("Alex")
        assert.Regexp(t, regexp.MustCompile("Alex$"), res)
    })
}

You can also implement setup/teardown this way using defer functions.

Benchmarking

There are tools for benchmarking. See docs

Fuzzing

There are tools for fuzzing tests. See docs