Go Testing

Writing and running tests in Go applications

๐Ÿงช What is Testing in Go?

Go has built-in testing support with the testing package. Write unit tests, benchmarks, and examples to ensure your code works correctly and performs well.


// Simple test example
package main

import "testing"

func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}
                                    

Output:

$ go test
PASS
ok example 0.001s

Go Testing Features

โœ…

Unit Tests

Test individual functions

func TestFunction(t *testing.T)
๐Ÿ“Š

Benchmarks

Measure performance

func BenchmarkFunction(b *testing.B)
๐Ÿ“

Examples

Testable documentation

func ExampleFunction()
๐ŸŽฏ

Table Tests

Test multiple scenarios

tests := []struct{...}

๐Ÿ”น Basic Unit Testing

Create and run unit tests for your functions:

// math.go
package main

func Multiply(a, b int) int {
    return a * b
}

func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// math_test.go
package main

import (
    "testing"
)

func TestMultiply(t *testing.T) {
    result := Multiply(4, 5)
    expected := 20
    
    if result != expected {
        t.Errorf("Multiply(4, 5) = %d; want %d", result, expected)
    }
}

func TestDivide(t *testing.T) {
    result, err := Divide(10, 2)
    if err != nil {
        t.Errorf("Unexpected error: %v", err)
    }
    
    expected := 5.0
    if result != expected {
        t.Errorf("Divide(10, 2) = %f; want %f", result, expected)
    }
}

func TestDivideByZero(t *testing.T) {
    _, err := Divide(10, 0)
    if err == nil {
        t.Error("Expected error for division by zero")
    }
}

Output:

$ go test -v
=== RUN TestMultiply
--- PASS: TestMultiply (0.00s)
=== RUN TestDivide
--- PASS: TestDivide (0.00s)
=== RUN TestDivideByZero
--- PASS: TestDivideByZero (0.00s)
PASS

๐Ÿ”น Table-Driven Tests

Test multiple scenarios efficiently with table tests:

package main

import "testing"

func IsEven(n int) bool {
    return n%2 == 0
}

func TestIsEven(t *testing.T) {
    tests := []struct {
        name     string
        input    int
        expected bool
    }{
        {"even positive", 4, true},
        {"odd positive", 5, false},
        {"even negative", -2, true},
        {"odd negative", -3, false},
        {"zero", 0, true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := IsEven(tt.input)
            if result != tt.expected {
                t.Errorf("IsEven(%d) = %v; want %v", 
                    tt.input, result, tt.expected)
            }
        })
    }
}

Output:

$ go test -v
=== RUN TestIsEven
=== RUN TestIsEven/even_positive
--- PASS: TestIsEven/even_positive (0.00s)
=== RUN TestIsEven/odd_positive
--- PASS: TestIsEven/odd_positive (0.00s)
=== RUN TestIsEven/zero
--- PASS: TestIsEven/zero (0.00s)
PASS

๐Ÿ”น Benchmark Tests

Measure and compare performance of your functions:

package main

import (
    "strings"
    "testing"
)

func ConcatString(strs []string) string {
    var result string
    for _, s := range strs {
        result += s
    }
    return result
}

func ConcatStringBuilder(strs []string) string {
    var builder strings.Builder
    for _, s := range strs {
        builder.WriteString(s)
    }
    return builder.String()
}

func BenchmarkConcatString(b *testing.B) {
    strs := []string{"hello", " ", "world", "!"}
    
    for i := 0; i < b.N; i++ {
        ConcatString(strs)
    }
}

func BenchmarkConcatStringBuilder(b *testing.B) {
    strs := []string{"hello", " ", "world", "!"}
    
    for i := 0; i < b.N; i++ {
        ConcatStringBuilder(strs)
    }
}

Output:

$ go test -bench=.
BenchmarkConcatString-8 10000000 150 ns/op
BenchmarkConcatStringBuilder-8 20000000 75 ns/op
PASS

๐Ÿ”น Testing HTTP Handlers

Test HTTP handlers and web services:

package main

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func userHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
        var user User
        json.NewDecoder(r.Body).Decode(&user)
        
        if user.Name == "" {
            http.Error(w, "Name required", http.StatusBadRequest)
            return
        }
        
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(user)
    }
}

func TestUserHandler(t *testing.T) {
    // Test valid user
    userJSON := `{"name":"Alice","age":30}`
    req := httptest.NewRequest("POST", "/user", strings.NewReader(userJSON))
    req.Header.Set("Content-Type", "application/json")
    
    rr := httptest.NewRecorder()
    userHandler(rr, req)
    
    if rr.Code != http.StatusOK {
        t.Errorf("Expected status 200, got %d", rr.Code)
    }
    
    // Test invalid user
    invalidJSON := `{"age":25}`
    req = httptest.NewRequest("POST", "/user", strings.NewReader(invalidJSON))
    rr = httptest.NewRecorder()
    userHandler(rr, req)
    
    if rr.Code != http.StatusBadRequest {
        t.Errorf("Expected status 400, got %d", rr.Code)
    }
}

Output:

$ go test -v
=== RUN TestUserHandler
--- PASS: TestUserHandler (0.00s)
PASS

๐Ÿ”น Test Coverage

Measure how much of your code is tested:

// calculator.go
package main

func Add(a, b int) int {
    return a + b
}

func Subtract(a, b int) int {
    return a - b
}

func IsPositive(n int) bool {
    if n > 0 {
        return true
    }
    return false
}

// calculator_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    if Add(2, 3) != 5 {
        t.Error("Add failed")
    }
}

func TestIsPositive(t *testing.T) {
    if !IsPositive(5) {
        t.Error("IsPositive failed for positive number")
    }
    
    if IsPositive(-1) {
        t.Error("IsPositive failed for negative number")
    }
}

Output:

$ go test -cover
PASS
coverage: 75.0% of statements

$ go test -coverprofile=coverage.out
$ go tool cover -html=coverage.out

๐Ÿง  Test Your Knowledge

What is the naming convention for test functions in Go?