Golang std testing: Difference between revisions

From wikinotes
Line 30: Line 30:
<blockquote>
<blockquote>
The builtin go test framework is fairly minimalist.<br>
The builtin go test framework is fairly minimalist.<br>
Tests are just functions, but you can define subtests with <code>t.Run(...)</code>.
Tests are just functions, but you can define subtests with <code>t.Run(...)</code>.<br>
There are no builtin assertions.


Tests are typically kept alongside code.
Tests are typically kept alongside code.

Revision as of 04:37, 18 July 2022

Go ships with a minimalist test suite.

Documentation

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

Usage

go test                              # run tests in CWD
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, but you can define subtests with t.Run(...).
There are no builtin assertions.

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

1:1 function:test with subtests

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 using t.Run(...)
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: "Valid Name", name: "Adam", },
        { test: "Nil Name", name: nil, },
        { test: "Invalid Name", 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...")
            }
        }
    }
}

Override Function

Go allows you to assign a function to a global variable.
You abstract a real method call with this substitute,
and override the substitute within a test.

// foo.go
var removeFile = os.Remove

func DoThing(path string) {
    removeFile(path)
}
// foo_test.go

func TestDoThing(t *testing.T) {
    var removedPath string
    removeFile = func(path string) {
        removedPath = path
    }

    DoThing("/var/abc.txt")
    if removedPath != "/var/abc.txt" {
        t.Errorf("Failed because...")
    }
}

Components

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