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