Go testing best practices including table-driven tests, test helpers, benchmarking, race detection, coverage analysis, and integration testing patterns. Use when writing or improving Go tests.
This skill provides comprehensive Go testing patterns extending common testing principles with Go-specific idioms.
Use the standard go test with table-driven tests as the primary pattern.
The idiomatic Go testing pattern:
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{
name: "valid email",
email: "user@example.com",
wantErr: false,
},
{
name: "missing @",
email: "userexample.com",
wantErr: true,
},
{
name: "empty string",
email: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateEmail(tt.email)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
tt.email, err, tt.wantErr)
}
})
}
}
Benefits:
t.Parallel()t.Run()Use t.Helper() to mark helper functions:
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func assertEqual(t *testing.T, got, want interface{}) {
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}
Benefits:
Use t.Cleanup() for resource cleanup:
func testDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open test db: %v", err)
}
// Cleanup runs after test completes
t.Cleanup(func() {
if err := db.Close(); err != nil {
t.Errorf("failed to close db: %v", err)
}
})
return db
}
func TestUserRepository(t *testing.T) {
db := testDB(t)
repo := NewUserRepository(db)
// ... test logic
}
Always run tests with the -race flag to detect data races:
go test -race ./...
In CI/CD:
- name: Test with race detector
run: go test -race -timeout 5m ./...
Why:
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Fail if coverage below 80%
go test -cover ./... | grep -E 'coverage: [0-7][0-9]\.[0-9]%' && exit 1
func BenchmarkValidateEmail(b *testing.B) {
email := "user@example.com"
b.ResetTimer()
for i := 0; i < b.N; i++ {
ValidateEmail(email)
}
}
Run benchmarks:
go test -bench=. -benchmem
Compare benchmarks:
go test -bench=. -benchmem > old.txt
# make changes
go test -bench=. -benchmem > new.txt
benchstat old.txt new.txt
type UserRepository interface {
GetUser(id string) (*User, error)
}
type mockUserRepository struct {
users map[string]*User
err error
}
func (m *mockUserRepository) GetUser(id string) (*User, error) {
if m.err != nil {
return nil, m.err
}
return m.users[id], nil
}
func TestUserService(t *testing.T) {
mock := &mockUserRepository{
users: map[string]*User{
"1": {ID: "1", Name: "Alice"},
},
}
service := NewUserService(mock)
// ... test logic
}
//go:build integration
// +build integration
package user_test
func TestUserRepository_Integration(t *testing.T) {
// ... integration test
}
Run integration tests:
go test -tags=integration ./...
func TestWithPostgres(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
// Setup test container
ctx := context.Background()
container, err := testcontainers.GenericContainer(ctx, ...)
assertNoError(t, err)
t.Cleanup(func() {
container.Terminate(ctx)
})
// ... test logic
}
package/
├── user.go
├── user_test.go # Unit tests
├── user_integration_test.go # Integration tests
└── testdata/ # Test fixtures
└── users.json
// Black-box testing (external perspective)
package user_test
// White-box testing (internal access)
package user
func TestUserHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/users/1", nil)
rec := httptest.NewRecorder()
handler := NewUserHandler(mockRepo)
handler.ServeHTTP(rec, req)
assertEqual(t, rec.Code, http.StatusOK)
}
func TestWithTimeout(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
err := SlowOperation(ctx)
if !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("expected timeout error, got %v", err)
}
}
t.Parallel() for independent teststesting.Short() to skip slow testst.TempDir() for temporary directoriest.Setenv() for environment variablesinit() in test filesCopy a source-pinned command for your client. You run it yourself.
Destination: .claude/skills/golang-testing · pinned to the source commit
git clone https://github.com/affaan-m/ECC.git
cd ECC
git checkout d8409a4b0813771235555e32e3d8046a73988bfa
mkdir -p ".claude/skills/golang-testing"
cp -r ".kiro/skills/golang-testing" ".claude/skills/golang-testing"Review the source before running. This copies files into your project; it is not a one-click install and does not verify runtime safety.
Scanner static-checks@0.1.0 · commit d8409a4b0813. Static checks cannot prove runtime safety – review the source and the exact diff before installing. How checks work.
No static rules matched. This is not a safety guarantee.