golang testing explained

A practical introduction to testing in Go, including unit tests, best practices, and tools.

golang testing explained

Testing is a core part of Go development. The Go standard library provides a powerful testing package that makes it easy to write unit tests, benchmarks, and even example code.

why test in go?

  • Ensure code correctness
  • Prevent regressions
  • Enable refactoring
  • Document code behavior

writing your first test

Create a file ending with _test.go, for example, math_test.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package math

import "testing"

func TestAdd(t *testing.T) {
    got := Add(2, 3)
    want := 5
    if got != want {
        t.Errorf("Add(2, 3) = %d; want %d", got, want)
    }
}

running tests

Use the following command:

1
go test

table-driven tests

Go encourages table-driven tests for better coverage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }
    for _, tt := range tests {
        got := Add(tt.a, tt.b)
        if got != tt.want {
            t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
        }
    }
}

table-driven tests with testify

You can use Testify with Table-driven tests to combine table-based tests for more readable and maintainable assertions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestAddTableDrivenWithTestify(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }
    for _, tt := range tests {
        t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) {
            assert.Equal(t, tt.want, Add(tt.a, tt.b))
        })
    }
}

test coverage

Check your test coverage:

1
go test -cover

useful testing tools

  • go test (built-in)
  • stretchr/testify (assertions)
  • golang/mock (mocking)

best practices

  • Keep tests fast and isolated
  • Use descriptive test names
  • Test edge cases
  • Run tests in CI/CD pipelines

advanced testing techniques

subtests

Subtests allow you to group related tests and run them independently.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func TestMathOperations(t *testing.T) {
    t.Run("Add", func(t *testing.T) {
        if Add(2, 2) != 4 {
            t.Error("Add failed")
        }
    })
    t.Run("Subtract", func(t *testing.T) {
        if Subtract(5, 3) != 2 {
            t.Error("Subtract failed")
        }
    })
}

benchmarks

Go makes it easy to write benchmarks to measure performance. Benchmark functions start with Benchmark and use a *testing.B parameter:

1
2
3
4
5
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

Run benchmarks with:

1
go test -bench .

mocking and interfaces

For example, use interfaces are used to mock dependencies for more isolated tests (e.g.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type DB interface {
    GetUser(id int) (User, error)
}

func GetUserName(db DB, id int) (string, error) {
    user, err := db.GetUser(id)
    if err != nil {
        return "", err
    }
    return user.Name, nil
}

You can then create a mock implementation of DB for testing.

parallel tests

Parallel to speed up test suites, Go supports running tests in support of running testing.

1
2
3
4
func TestParallel(t *testing.T) {
    t.Parallel()
    // test code here
}

fuzz testing

Fuzz testing is a tool that helps find edge cases automatically using fuzz tests in Go 1.18+ to support fuzzle testing.

1
2
3
4
5
6
func FuzzAdd(f *testing.F) {
    f.Add(1, 2)
    f.Fuzz(func(t *testing.T, a int, b int) {
        _ = Add(a, b)
    })
}

using testify for assertions and mocking

Testify is a popular Go testing toolkit that provides more expressive assertions and mocking capabilities.

assertions example

First, install Testify:

1
go get github.com/stretchr/testify

Then use it in your tests:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestAddWithTestify(t *testing.T) {
    assert := assert.New(t)
    assert.Equal(5, Add(2, 3), "they should be equal")
    assert.NotEqual(6, Add(2, 3), "they should not be equal")
}

mocking example

Testify’s mock package helps you create mock objects for interface-based code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import (
    "testing"
    "github.com/stretchr/testify/mock"
)

type MockDB struct {
    mock.Mock
}

func (m *MockDB) GetUser(id int) (User, error) {
    args := m.Called(id)
    return args.Get(0).(User), args.Error(1)
}

func TestGetUserNameWithMock(t *testing.T) {
    mdb := new(MockDB)
    mdb.On("GetUser", 1).Return(User{Name: "Alice"}, nil)

    name, err := GetUserName(mdb, 1)
    assert := assert.New(t)
    assert.NoError(err)
    assert.Equal("Alice", name)
    mdb.AssertExpectations(t)
}

Testify also makes your tests more readable and maintainable, especially for larger projects.


The simple but powerful Testing in Go is easy and powerful. Start small, write tests as you code & enjoy safer, more reliable software!

Built with Hugo
Theme Stack designed by Jimmy