Skip to main content
โšก Calmops

Testing Basics with testing package

Testing Basics with testing package

Go’s testing package provides a simple yet powerful framework for writing tests. Testing is a first-class citizen in Go.

Writing Tests

Basic Test

// math.go
package main

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

// math_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

Running Tests

# Run all tests
go test

# Run with verbose output
go test -v

# Run specific test
go test -run TestAdd

# Run with coverage
go test -cover

# Generate coverage report
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

Test Organization

Table-Driven Tests

package main

import "testing"

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -2, -3, -5},
        {"mixed numbers", 5, -3, 2},
        {"zero", 0, 0, 0},
    }

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

Subtests

package main

import "testing"

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

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

Test Helpers

Helper Functions

package main

import "testing"

func TestWithHelper(t *testing.T) {
    t.Helper()  // Mark as helper function
    // Test code
}

func assertEqual(t *testing.T, got, want interface{}) {
    t.Helper()
    if got != want {
        t.Errorf("got %v; want %v", got, want)
    }
}

func TestAdd(t *testing.T) {
    assertEqual(t, Add(2, 3), 5)
}

Benchmarking

Basic Benchmark

package main

import "testing"

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

func BenchmarkAddLarge(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1000000, 2000000)
    }
}

Running Benchmarks

# Run benchmarks
go test -bench=.

# Run specific benchmark
go test -bench=BenchmarkAdd

# Run with memory stats
go test -bench=. -benchmem

# Run for specific duration
go test -bench=. -benchtime=10s

Test Coverage

Checking Coverage

# Generate coverage profile
go test -coverprofile=coverage.out

# View coverage percentage
go test -cover

# View coverage in HTML
go tool cover -html=coverage.out

Coverage Example

package main

import "testing"

func TestAddCoverage(t *testing.T) {
    // Test positive case
    if Add(2, 3) != 5 {
        t.Error("positive case failed")
    }

    // Test negative case
    if Add(-2, -3) != -5 {
        t.Error("negative case failed")
    }

    // Test zero case
    if Add(0, 0) != 0 {
        t.Error("zero case failed")
    }
}

Best Practices

โœ… Good: Clear Test Names

// DO: Use descriptive test names
func TestAddPositiveNumbers(t *testing.T) {
    // ...
}

func TestAddNegativeNumbers(t *testing.T) {
    // ...
}

โŒ Bad: Vague Test Names

// DON'T: Use unclear names
func TestAdd1(t *testing.T) {
    // ...
}

func TestAdd2(t *testing.T) {
    // ...
}

โœ… Good: Test Edge Cases

// DO: Test edge cases
func TestDivide(t *testing.T) {
    tests := []struct {
        a, b     float64
        expected float64
    }{
        {10, 2, 5},      // normal case
        {0, 5, 0},       // zero numerator
        {-10, 2, -5},    // negative
    }
    // ...
}

โœ… Good: Use Table-Driven Tests

// DO: Use table-driven tests for multiple cases
func TestAdd(t *testing.T) {
    tests := []struct {
        a, b     int
        expected int
    }{
        {2, 3, 5},
        {-2, -3, -5},
        {0, 0, 0},
    }

    for _, tt := range tests {
        t.Run("", func(t *testing.T) {
            if Add(tt.a, tt.b) != tt.expected {
                t.Error("test failed")
            }
        })
    }
}

Summary

Go’s testing package provides:

  1. Simple test syntax with *testing.T
  2. Table-driven tests for multiple cases
  3. Subtests for organization
  4. Benchmarking for performance testing
  5. Coverage analysis for quality metrics

These features make testing a natural part of Go development.

Comments