| name | golang-testing |
| description | Go testing standards: table-driven tests, testify, mockgen, coverage measurement, race-safe tests, benchmark correctness, and test isolation patterns. Use when writing, reviewing, or running Go tests.
|
| allowed-tools | Bash(go test *), Bash(mockgen *), Bash(go generate *), Bash(go tool cover *), Bash(gotestsum *), Read, Grep, Glob |
Go Testing Standards
Tests in Go are first-class code. They live next to the implementation, run fast via go test,
and the race detector is always available. This skill enforces patterns that make tests
readable, reliable, and maintainable.
1. File & Package Naming
mypackage/
├── service.go # production code — package mypackage
├── service_test.go # white-box tests — package mypackage
└── service_ext_test.go # black-box tests — package mypackage_test
Convention: one _test.go file per source file. Black-box tests (_test package suffix)
test the public API and are preferred for packages with stable interfaces.
2. Test Naming
func TestGetUser_ValidID_ReturnsUser(t *testing.T) { ... }
func TestGetUser_NotFound_ReturnsErrNotFound(t *testing.T) { ... }
func TestGetUser_DBError_WrapsError(t *testing.T) { ... }
func TestGetUser(t *testing.T) {
t.Run("valid ID returns user", func(t *testing.T) { ... })
t.Run("not found returns ErrNotFound", func(t *testing.T) { ... })
t.Run("DB error is wrapped", func(t *testing.T) { ... })
}
3. Table-Driven Tests
The standard Go pattern for testing multiple scenarios:
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{name: "positive numbers", a: 1, b: 2, expected: 3},
{name: "negative numbers", a: -1, b: -2, expected: -3},
{name: "zero", a: 0, b: 0, expected: 0},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := Add(tc.a, tc.b)
if got != tc.expected {
t.Errorf("Add(%d, %d) = %d, want %d", tc.a, tc.b, got, tc.expected)
}
})
}
}
4. testify Patterns
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
svc, err := NewService(cfg)
require.NoError(t, err)
require.NotNil(t, svc)
result, err := svc.Process("input")
assert.NoError(t, err)
assert.Equal(t, "expected", result)
assert.Contains(t, result, "partial")
_, err = svc.Process("")
var ve *ValidationError
assert.ErrorAs(t, err, &ve)
assert.Equal(t, "input", ve.Field)
}
Rule: use require for anything that would make subsequent assertions meaningless.
Use assert for independent checks.
5. Mocks with mockgen
type UserRepository interface {
FindByID(ctx context.Context, id string) (*User, error)
Save(ctx context.Context, user *User) error
}
go generate ./...
mockgen -destination=mocks/user_repo_mock.go -package=mocks \
github.com/myorg/myproject/internal/user UserRepository
import "github.com/myorg/myproject/internal/user/mocks"
func TestService_Process(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
repo := mocks.NewMockUserRepository(ctrl)
repo.EXPECT().
FindByID(gomock.Any(), "user-123").
Return(&User{ID: "user-123", Name: "Alice"}, nil)
svc := NewService(repo)
result, err := svc.Process(context.Background(), "user-123")
require.NoError(t, err)
assert.Equal(t, "Alice", result.Name)
}
6. Test Helpers
func createTestUser(t *testing.T, db *DB, name string) *User {
t.Helper()
user := &User{Name: name}
require.NoError(t, db.Save(user))
return user
}
func TestWithDB(t *testing.T) {
db := connectTestDB(t)
t.Cleanup(func() { db.Close() })
}
func TestFileProcessor(t *testing.T) {
dir := t.TempDir()
}
7. Coverage Commands
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -func=coverage.out | tail -1
go tool cover -html=coverage.out -o coverage.html
open coverage.html
go test -cover ./internal/user/...
go test -coverprofile=coverage.out -coverpkg=./internal/... ./...
Thresholds (рекомендуемые):
- Новый код: ≥ 80%
- Существующий: ≥ 70% (предупреждение), ≥ 50% (нельзя снижать)
- Критические пакеты (auth, payments): ≥ 90%
8. Race-Safe Tests
go test -race ./...
go test -race -count=1 ./...
func TestConcurrent(t *testing.T) {
t.Parallel() // тест запускается параллельно — должен быть race-safe
...
}
9. Build Tags for Integration Tests
package mypackage_test
10. Key Commands
go test -race -count=1 ./...
go test -run TestGetUser ./internal/user/...
go test -run "TestGetUser/not_found" ./internal/user/...
go test -v -race ./...
go test -race -coverprofile=coverage.out ./... && go tool cover -func=coverage.out | tail -1
gotestsum --format=testdox ./...