Advanced Testing

Back

Loading concept...

๐Ÿงช Go Advanced Testing: Your Quality Shield

One Metaphor: Think of testing like being a detective checking if your robot helper works perfectly. You give it puzzles, watch how it behaves, and make sure it never makes mistakes!


๐ŸŽฏ What Youโ€™ll Learn

Imagine you built a LEGO castle. Before showing it to friends, youโ€™d:

  • Check if all pieces are connected
  • Test if doors open properly
  • Make sure it doesnโ€™t fall apart

Thatโ€™s exactly what advanced testing does for your Go code!


๐Ÿ“Š Table-Driven Tests

The Story

Picture this: Youโ€™re a teacher grading math homework. Instead of writing separate rules for each student, you make one checklist and run every studentโ€™s paper through it!

func TestAdd(t *testing.T) {
  tests := []struct {
    name     string
    a, b     int
    expected int
  }{
    {"two positives", 2, 3, 5},
    {"with zero", 5, 0, 5},
    {"negatives", -1, -2, -3},
  }

  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      got := Add(tt.a, tt.b)
      if got != tt.expected {
        t.Errorf("got %d, want %d",
          got, tt.expected)
      }
    })
  }
}

Why This Is Amazing

  • One test function handles many scenarios
  • Add new cases = just add a row to the table
  • Each test has a name so you know which failed
graph TD A["Define Test Cases"] --> B["Loop Through Each"] B --> C["Run t.Run"] C --> D{Pass?} D -->|Yes| E["โœ… Next Case"] D -->|No| F["โŒ Report Error"] E --> B F --> B

๐ŸŒ Testing HTTP Handlers

The Story

Your code has a door (HTTP handler) where visitors knock and ask for things. You need a pretend visitor to test if the door responds correctly!

Creating a Test Handler

func HelloHandler(w http.ResponseWriter,
  r *http.Request) {
  name := r.URL.Query().Get("name")
  if name == "" {
    name = "World"
  }
  fmt.Fprintf(w, "Hello, %s!", name)
}

Testing It

func TestHelloHandler(t *testing.T) {
  // Create fake request
  req := httptest.NewRequest(
    "GET",
    "/?name=Alice",
    nil,
  )

  // Create fake response recorder
  rr := httptest.NewRecorder()

  // Call the handler
  HelloHandler(rr, req)

  // Check the response
  if rr.Code != http.StatusOK {
    t.Errorf("wrong status: %d", rr.Code)
  }

  expected := "Hello, Alice!"
  if rr.Body.String() != expected {
    t.Errorf("got %s, want %s",
      rr.Body.String(), expected)
  }
}

The Magic Parts

Component What It Does
httptest.NewRequest Creates a pretend visitor
httptest.NewRecorder Catches what the door says back
rr.Code The status number (200 = OK!)
rr.Body The actual message returned

๐Ÿ“ฆ The httptest Package

Your Testing Toolbox

The httptest package gives you superpowers to test web code without starting a real server!

Tool 1: NewRecorder

Records what your handler says:

rr := httptest.NewRecorder()
// Use rr as http.ResponseWriter
handler.ServeHTTP(rr, req)
// Now check rr.Code and rr.Body

Tool 2: NewRequest

Creates fake HTTP requests:

// GET request
req := httptest.NewRequest("GET", "/", nil)

// POST with body
body := strings.NewReader(`{"name":"Go"}`)
req := httptest.NewRequest(
  "POST", "/api", body,
)
req.Header.Set(
  "Content-Type",
  "application/json",
)

Tool 3: NewServer

Creates a real temporary server for bigger tests:

server := httptest.NewServer(
  http.HandlerFunc(HelloHandler),
)
defer server.Close()

// Now use server.URL to make real requests!
resp, _ := http.Get(server.URL + "?name=Go")
graph TD A["httptest Package"] --> B["NewRecorder"] A --> C["NewRequest"] A --> D["NewServer"] B --> E["Capture Response"] C --> F["Fake HTTP Request"] D --> G["Real Test Server"]

๐Ÿ”— Integration Testing

The Story

Unit tests check one LEGO piece. Integration tests check if many pieces work together like a real castle!

Example: Testing Database + Handler

func TestUserAPI(t *testing.T) {
  // Setup: real database connection
  db := setupTestDB(t)
  defer db.Close()

  // Create handler with real DB
  handler := NewUserHandler(db)
  server := httptest.NewServer(handler)
  defer server.Close()

  // Test creating a user
  resp, err := http.Post(
    server.URL+"/users",
    "application/json",
    strings.NewReader(
      `{"name":"Alice"}`,
    ),
  )
  if err != nil {
    t.Fatal(err)
  }

  if resp.StatusCode != 201 {
    t.Errorf("create failed: %d",
      resp.StatusCode)
  }

  // Test fetching the user
  resp, _ = http.Get(
    server.URL + "/users/1",
  )
  // ... verify response
}

Key Differences

Unit Test Integration Test
Tests one function Tests multiple systems
Uses mocks/fakes Uses real dependencies
Super fast Slower but realistic
Catches logic bugs Catches connection bugs

Best Practices

  1. Isolate tests - each test gets fresh data
  2. Clean up - remove test data after
  3. Use test tags - separate from unit tests
//go:build integration
// Run with: go test -tags=integration

๐Ÿ“ Test Fixtures

The Story

Fixtures are like recipe ingredients you prepare before cooking. You set up test data once and reuse it!

Loading Test Data

Create a testdata folder (Go ignores this in builds):

myproject/
โ”œโ”€โ”€ handler.go
โ”œโ”€โ”€ handler_test.go
โ””โ”€โ”€ testdata/
    โ”œโ”€โ”€ user.json
    โ””โ”€โ”€ expected.json

Using Fixtures

func TestParseUser(t *testing.T) {
  // Load fixture file
  data, err := os.ReadFile(
    "testdata/user.json",
  )
  if err != nil {
    t.Fatal(err)
  }

  // Use in test
  var user User
  json.Unmarshal(data, &user)

  if user.Name != "Test User" {
    t.Error("wrong name")
  }
}

Golden Files Pattern

Compare output against saved โ€œgoldenโ€ files:

func TestRender(t *testing.T) {
  got := RenderTemplate(data)

  golden := "testdata/expected.html"

  // Update golden files with -update flag
  if *update {
    os.WriteFile(golden, got, 0644)
    return
  }

  want, _ := os.ReadFile(golden)
  if !bytes.Equal(got, want) {
    t.Error("output doesn't match")
  }
}
graph TD A["Test Fixtures"] --> B["testdata/ folder"] B --> C["JSON files"] B --> D["Golden files"] B --> E["Sample inputs"] C --> F["Load & Parse"] D --> G["Compare Output"] E --> H["Feed to Functions"]

๐ŸŽฒ Fuzzing

The Story

Imagine a robot randomly pressing buttons on your calculator millions of times to find bugs youโ€™d never think of!

What Fuzzing Does

  • Generates random inputs automatically
  • Finds edge cases humans miss
  • Saves crashing inputs as proof

Writing a Fuzz Test

func FuzzReverse(f *testing.F) {
  // Add seed corpus (starting examples)
  f.Add("hello")
  f.Add("ไธ–็•Œ")
  f.Add("")

  // The fuzz target
  f.Fuzz(func(t *testing.T, s string) {
    rev := Reverse(s)
    doubleRev := Reverse(rev)

    // Property: reversing twice = original
    if s != doubleRev {
      t.Errorf("double reverse failed")
    }
  })
}

Running Fuzz Tests

# Run for 30 seconds
go test -fuzz=FuzzReverse -fuzztime=30s

# Found failures saved in testdata/fuzz/

Key Concepts

Term Meaning
Seed Corpus Starting examples you provide
Generated Corpus New inputs fuzzer creates
Crash When input causes failure
Coverage Code paths the fuzzer explored

When Fuzzing Shines

  • Parsing functions (JSON, XML, etc.)
  • String manipulation
  • Encoding/decoding
  • Any function taking []byte or string
graph TD A["Start Fuzzing"] --> B["Use Seed Corpus"] B --> C["Generate Random Input"] C --> D["Run Function"] D --> E{Crash?} E -->|Yes| F["Save Failing Input"] E -->|No| G["Mutate Input"] G --> C F --> H["Fix Bug!"]

๐Ÿš€ Putting It All Together

Your Testing Pyramid

graph TD A["Unit Tests - Many & Fast"] --> B["Table-Driven"] A --> C["httptest"] D["Integration Tests - Some"] --> E["Real DB + Server"] F["Fuzz Tests - Continuous"] --> G["Find Edge Cases"] B --> H["๐Ÿ† Confident Code"] C --> H E --> H G --> H

Quick Reference

Tool Use When
Table-driven Testing many inputs
httptest Testing HTTP handlers
Integration Testing system connections
Fixtures Need consistent test data
Fuzzing Finding unknown edge cases

๐ŸŽ‰ You Did It!

Youโ€™re now equipped with Goโ€™s advanced testing arsenal:

  1. โœ… Table-driven tests - One function, many scenarios
  2. โœ… HTTP testing - Fake visitors for your handlers
  3. โœ… httptest package - Your testing toolbox
  4. โœ… Integration tests - Check the whole system
  5. โœ… Fixtures - Prepared test ingredients
  6. โœ… Fuzzing - Robot button-masher finds bugs

Go forth and write bulletproof code! ๐Ÿ›ก๏ธ

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.