Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Senior Go developer skill covering concurrency patterns, gRPC microservices, generics, pprof optimization, and idiomatic testing.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/testing.md
1# Testing and Benchmarking23## Table-Driven Tests45```go6package math78import "testing"910func Add(a, b int) int {11return a + b12}1314func TestAdd(t *testing.T) {15tests := []struct {16name string17a, b int18expected int19}{20{"positive numbers", 2, 3, 5},21{"negative numbers", -2, -3, -5},22{"mixed signs", -2, 3, 1},23{"zeros", 0, 0, 0},24{"large numbers", 1000000, 2000000, 3000000},25}2627for _, tt := range tests {28t.Run(tt.name, func(t *testing.T) {29result := Add(tt.a, tt.b)30if result != tt.expected {31t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)32}33})34}35}36```3738## Subtests and Parallel Execution3940```go41func TestParallel(t *testing.T) {42tests := []struct {43name string44input string45want string46}{47{"lowercase", "hello", "HELLO"},48{"uppercase", "WORLD", "WORLD"},49{"mixed", "HeLLo", "HELLO"},50}5152for _, tt := range tests {53tt := tt // Capture range variable for parallel tests54t.Run(tt.name, func(t *testing.T) {55t.Parallel() // Run subtests in parallel5657result := strings.ToUpper(tt.input)58if result != tt.want {59t.Errorf("got %q, want %q", result, tt.want)60}61})62}63}64```6566## Test Helpers and Setup/Teardown6768```go69func TestWithSetup(t *testing.T) {70// Setup71db := setupTestDB(t)72defer cleanupTestDB(t, db)7374tests := []struct {75name string76user User77}{78{"valid user", User{Name: "John", Email: "[email protected]"}},79{"empty name", User{Name: "", Email: "[email protected]"}},80}8182for _, tt := range tests {83t.Run(tt.name, func(t *testing.T) {84err := db.SaveUser(tt.user)85if err != nil {86t.Fatalf("SaveUser failed: %v", err)87}88})89}90}9192// Helper function (doesn't show in stack trace)93func setupTestDB(t *testing.T) *DB {94t.Helper()9596db, err := NewDB(":memory:")97if err != nil {98t.Fatalf("failed to create test DB: %v", err)99}100return db101}102103func cleanupTestDB(t *testing.T, db *DB) {104t.Helper()105106if err := db.Close(); err != nil {107t.Errorf("failed to close DB: %v", err)108}109}110```111112## Mocking with Interfaces113114```go115// Interface to mock116type EmailSender interface {117Send(to, subject, body string) error118}119120// Mock implementation121type MockEmailSender struct {122SentEmails []Email123ShouldFail bool124}125126type Email struct {127To, Subject, Body string128}129130func (m *MockEmailSender) Send(to, subject, body string) error {131if m.ShouldFail {132return fmt.Errorf("failed to send email")133}134m.SentEmails = append(m.SentEmails, Email{to, subject, body})135return nil136}137138// Test using mock139func TestUserService_Register(t *testing.T) {140mockSender := &MockEmailSender{}141service := NewUserService(mockSender)142143err := service.Register("[email protected]")144if err != nil {145t.Fatalf("Register failed: %v", err)146}147148if len(mockSender.SentEmails) != 1 {149t.Errorf("expected 1 email sent, got %d", len(mockSender.SentEmails))150}151152email := mockSender.SentEmails[0]153if email.To != "[email protected]" {154t.Errorf("expected email to [email protected], got %s", email.To)155}156}157```158159## Benchmarking160161```go162func BenchmarkAdd(b *testing.B) {163for i := 0; i < b.N; i++ {164Add(100, 200)165}166}167168// Benchmark with subtests169func BenchmarkStringOperations(b *testing.B) {170benchmarks := []struct {171name string172input string173}{174{"short", "hello"},175{"medium", strings.Repeat("hello", 10)},176{"long", strings.Repeat("hello", 100)},177}178179for _, bm := range benchmarks {180b.Run(bm.name, func(b *testing.B) {181for i := 0; i < b.N; i++ {182_ = strings.ToUpper(bm.input)183}184})185}186}187188// Benchmark with setup189func BenchmarkMapOperations(b *testing.B) {190m := make(map[string]int)191for i := 0; i < 1000; i++ {192m[fmt.Sprintf("key%d", i)] = i193}194195b.ResetTimer() // Don't count setup time196197for i := 0; i < b.N; i++ {198_ = m["key500"]199}200}201202// Parallel benchmark203func BenchmarkConcurrentAccess(b *testing.B) {204var counter int64205206b.RunParallel(func(pb *testing.PB) {207for pb.Next() {208atomic.AddInt64(&counter, 1)209}210})211}212213// Memory allocation benchmark214func BenchmarkAllocation(b *testing.B) {215b.ReportAllocs() // Report allocations216217for i := 0; i < b.N; i++ {218s := make([]int, 1000)219_ = s220}221}222```223224## Fuzzing (Go 1.18+)225226```go227func FuzzReverse(f *testing.F) {228// Seed corpus229testcases := []string{"hello", "world", "123", ""}230for _, tc := range testcases {231f.Add(tc)232}233234f.Fuzz(func(t *testing.T, input string) {235reversed := Reverse(input)236doubleReversed := Reverse(reversed)237238if input != doubleReversed {239t.Errorf("Reverse(Reverse(%q)) = %q, want %q", input, doubleReversed, input)240}241})242}243244// Fuzz with multiple parameters245func FuzzAdd(f *testing.F) {246f.Add(1, 2)247f.Add(0, 0)248f.Add(-1, 1)249250f.Fuzz(func(t *testing.T, a, b int) {251result := Add(a, b)252253// Properties that should always hold254if result < a && b >= 0 {255t.Errorf("Add(%d, %d) = %d; result should be >= a when b >= 0", a, b, result)256}257})258}259```260261## Test Coverage262263```go264// Run tests with coverage:265// go test -cover266// go test -coverprofile=coverage.out267// go tool cover -html=coverage.out268269func TestCalculate(t *testing.T) {270tests := []struct {271name string272input int273expected int274}{275{"zero", 0, 0},276{"positive", 5, 25},277{"negative", -3, 9},278}279280for _, tt := range tests {281t.Run(tt.name, func(t *testing.T) {282result := Calculate(tt.input)283if result != tt.expected {284t.Errorf("Calculate(%d) = %d; want %d", tt.input, result, tt.expected)285}286})287}288}289```290291## Race Detector292293```go294// Run with: go test -race295296func TestConcurrentAccess(t *testing.T) {297var counter int298var wg sync.WaitGroup299300// This will fail with -race if not synchronized301for i := 0; i < 10; i++ {302wg.Add(1)303go func() {304defer wg.Done()305counter++ // Data race!306}()307}308309wg.Wait()310}311312// Fixed version with mutex313func TestConcurrentAccessSafe(t *testing.T) {314var counter int315var mu sync.Mutex316var wg sync.WaitGroup317318for i := 0; i < 10; i++ {319wg.Add(1)320go func() {321defer wg.Done()322mu.Lock()323counter++324mu.Unlock()325}()326}327328wg.Wait()329330if counter != 10 {331t.Errorf("expected 10, got %d", counter)332}333}334```335336## Golden Files337338```go339import (340"os"341"path/filepath"342"testing"343)344345func TestRenderHTML(t *testing.T) {346data := Data{Title: "Test", Content: "Hello"}347result := RenderHTML(data)348349goldenFile := filepath.Join("testdata", "expected.html")350351if *update {352// Update golden file: go test -update353os.WriteFile(goldenFile, []byte(result), 0644)354}355356expected, err := os.ReadFile(goldenFile)357if err != nil {358t.Fatalf("failed to read golden file: %v", err)359}360361if result != string(expected) {362t.Errorf("output doesn't match golden file\ngot:\n%s\nwant:\n%s", result, expected)363}364}365366var update = flag.Bool("update", false, "update golden files")367```368369## Integration Tests370371```go372// integration_test.go373// +build integration374375package myapp376377import (378"testing"379"time"380)381382func TestIntegration(t *testing.T) {383if testing.Short() {384t.Skip("skipping integration test in short mode")385}386387// Long-running integration test388server := startTestServer(t)389defer server.Stop()390391time.Sleep(100 * time.Millisecond) // Wait for server392393client := NewClient(server.URL)394resp, err := client.Get("/health")395if err != nil {396t.Fatalf("health check failed: %v", err)397}398399if resp.Status != "ok" {400t.Errorf("expected status ok, got %s", resp.Status)401}402}403404// Run: go test -tags=integration405// Run short tests only: go test -short406```407408## Testable Examples409410```go411// Example tests that appear in godoc412func ExampleAdd() {413result := Add(2, 3)414fmt.Println(result)415// Output: 5416}417418func ExampleAdd_negative() {419result := Add(-2, -3)420fmt.Println(result)421// Output: -5422}423424// Unordered output425func ExampleKeys() {426m := map[string]int{"a": 1, "b": 2, "c": 3}427keys := Keys(m)428for _, k := range keys {429fmt.Println(k)430}431// Unordered output:432// a433// b434// c435}436```437438## Quick Reference439440| Command | Description |441|---------|-------------|442| `go test` | Run tests |443| `go test -v` | Verbose output |444| `go test -run TestName` | Run specific test |445| `go test -bench .` | Run benchmarks |446| `go test -cover` | Show coverage |447| `go test -race` | Run race detector |448| `go test -short` | Skip long tests |449| `go test -fuzz FuzzName` | Run fuzzing |450| `go test -cpuprofile cpu.prof` | CPU profiling |451| `go test -memprofile mem.prof` | Memory profiling |452