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:
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:
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:
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!