_test.go for test files, holding TestXXXX functions (this is similar to the BenchmarkXXXX functions for benchmarking)go test
func TestCrypto(t *testing.T) {
// do some tests
if err != nil {
t.ErrorF("failed test: %s", err)
}
}
testing.T is something you can report errors ont.Run()func SomeTest(t *testing.T) {
table := []struct {
name string
param int
outcome int
}{
{name: "test1", param: 1, outcome: 10},
{name: "test2", param: 3, outcome: 30},
}
}
for _, st := range table {
t.Run(st.name, func(t *testing.T) {
// do the test here
})
}
// this is the function we are testing
func someFunctionToTest(input string) bool {
return len(input) < 5
}
// we define a named type for our parameterised test
type parameterisedTest struct {
name string
input string
want bool
}
// we define a run method which we use as our input to t.Run
func (pt parameterisedTest) run(t *testing.T) {
got := someFunctionToTest(pt.input)
if pt.want != got {
t.Errorf("input %v, wanted %v, got %v", pt.input, pt.want, got)
}
}
// we define our table of tests
var tests = []parameterisedTest{
{name: "happy", input: "hi", want: true},
{name: "boundary 1", input: "hello", want: true},
{name: "boundary 2", input: "helloo", want: false},
}
// now everything is nicely separated in our actual test and is easier to follow
func TestSomeFunctionToTest(t *testing.T) {
for _, pt := range tests {
t.Run(pt.name, pt.run)
}
}
pt.run is a method value! It’s the run method bound to pt which is the particular parameterised test we are running!type checker interface {
check(*testing.T, string, string) bool
}
type subTest struct {
name string
shouldFail bool
checker checker
}
type checkGolden struct { /*...*/ }
func (c checkGolden) check(t *testing.T, got, want string) bool {
// implement checking logic here
}
type DB interface {
GetThing(string) (string, error)
}
var errShouldFail = errors.New("db should fail")
type mockDB struct {
shouldFail bool // our mock DB can have forced fail scenarios for testing
}
func (m mockDB) GetThing(key string) (string, error) {
// mockDB now conforms to the DB interface
if m.shouldFail {
// we can force an error case when the flag is set
return thing{}, fmt.Errorf("%s: %w", key, errShouldFail)
}
}
TestMainTestMain and testing.M is a way of doing initialisation before running tests, e.g. spinning up a databasefunc TestMain(m *testing.M) {
// setup
setupDatabase()
// run tests
rc := m.Run()
// teardown
teardownDatabase()
os.Exit(rc)
}
m.Run() is explicitly invokedos.Exit(), don’t defer since defers don’t run after os.Exit() is calledt.Cleanupt.Cleanup is a function used to unregister resources created for a test, an alternative to tearing down in TestMainfunc TestSomething(t *testing.T) {
dir := os.MkdirTemp("", "x")
t.Cleanup(func() { os.RemoveAll(dir) }) // register cleanup function
// test uses dir...
}
t.Cleanup registrations are similar to defer except they are preferred since from inside helper functions, a defer would run after the helper is called rather than after the test has completed:func newServer(t *testing.T) *Server {
s := startServer()
t.Cleanup(func(){ s.Close() }) // register cleanup in helper
return s
}
func TestDoStuff(t *testing.T) {
s := newServer(t)
// use s - cleanup happens after test completes
}
_test will mean you only have access to exported names (functions, constants, structs etc.) from your package