Frontend Labs
⋯

Day 10 — Testing in Go

Learning Objectives

  • Write table-driven tests that cover happy path, edge cases, and error cases
  • Use t.Run for subtests — readable output, independent failures
  • Run tests with -race to catch concurrency bugs early

Key Concepts

// Table-driven test pattern — the Go idiom
func TestStorage(t *testing.T) {
    tests := []struct {
        name    string
        key     string
        value   string
        wantErr bool
    }{
        {"normal set/get", "key1", "value1", false},
        {"empty key", "", "value", true},
        {"empty value", "key", "", false},
        {"long key", strings.Repeat("x", 1000), "v", true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            store := NewMemoryStorage(100)
            err := store.Set(tt.key, tt.value)
            if (err != nil) != tt.wantErr {
                t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}

Exercise: Storage Tests

// storage/storage_test.go
package storage_test

import (
    "sync"
    "testing"

    "github.com/yourname/secproxy/storage"
)

func TestMemoryStorage_SetGet(t *testing.T) {
    tests := []struct {
        name    string
        key     string
        value   string
        wantErr bool
    }{
        {"normal", "api_key_abc", "user:42", false},
        {"empty key", "", "value", true},
        {"overwrite", "key", "v1", false},
        {"unicode key", "用户/key", "value", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            s := storage.NewMemoryStorage(100)
            err := s.Set(tt.key, tt.value)
            if (err != nil) != tt.wantErr {
                t.Fatalf("Set() error=%v, wantErr=%v", err, tt.wantErr)
            }
            if err != nil {
                return
            }
            got, ok := s.Get(tt.key)
            if !ok {
                t.Fatalf("Get() key not found after Set()")
            }
            if got != tt.value {
                t.Errorf("Get() = %q, want %q", got, tt.value)
            }
        })
    }
}

func TestMemoryStorage_Concurrent(t *testing.T) {
    s := storage.NewMemoryStorage(10000)
    var wg sync.WaitGroup

    // 100 goroutines writing and reading concurrently
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", id)
            if err := s.Set(key, "value"); err != nil {
                t.Errorf("Set: %v", err)
            }
            s.Get(key)
        }(i)
    }
    wg.Wait()
}

// Run with: go test -race -v ./storage/...

Real-World Context

go test -race uses the Go race detector — it instruments memory accesses and catches concurrent map writes that would crash in production. Require -race in CI. The concurrent test above catches missing sync.RWMutex in MemoryStorage.


Generated from week2.md · 8 Oct 2026 16:36