Writing reliable tests keeps Go applications stable and maintainable. From basic web servers to complex microservices, good tests are essential for any Go project’s success. Let’s explore practical testing approaches in Go, starting with fundamentals and building up to advanced techniques.
If you’re new to Go programming, start with our Getting Started with Go guide. For everyone ready to learn testing, let’s begin.
Table of Contents
- Go’s Testing Package Basics
- Your First Go Test
- Smart Test Organization
- Testing Complex Types
- Coverage and Performance
- Testing External Dependencies
- Testing HTTP Handlers
- Test-Driven Development Tips
- Wrapping Up
Go’s Testing Package Basics
Go’s standard library includes the testing
package, giving you everything needed for effective testing without external frameworks. Here’s what makes it special:
- Test files end with
_test.go
- Test functions start with
Test
- Simple, clear testing API
- Built-in benchmarking tools
Your First Go Test
Let’s write a simple test. Start with calculator.go
:
package calculator
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
Code language: JavaScript (javascript)
Create the test file calculator_test.go
:
package calculator
import "testing"
func TestAdd(t *testing.T) {
x, y := 5, 3
expected := 8
result := Add(x, y)
if result != expected {
t.Errorf("Add(%d, %d) = %d; expected %d", x, y, result, expected)
}
}
func TestSubtract(t *testing.T) {
x, y := 5, 3
expected := 2
result := Subtract(x, y)
if result != expected {
t.Errorf("Subtract(%d, %d) = %d; expected %d", x, y, result, expected)
}
}
Code language: JavaScript (javascript)
Smart Test Organization
Keep your tests clean and maintainable with these methods:
Table-Driven Tests
Test multiple scenarios efficiently:
func TestAdd_TableDriven(t *testing.T) {
tests := []struct {
name string
x, y int
expected int
}{
{"positive numbers", 5, 3, 8},
{"negative numbers", -2, -3, -5},
{"zero values", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.x, tt.y)
if result != tt.expected {
t.Errorf("got %d, want %d", result, tt.expected)
}
})
}
}
Code language: JavaScript (javascript)
Helper Functions
Reduce repetition with helper functions:
func assertResult(t *testing.T, got, want int, name string) {
t.Helper()
if got != want {
t.Errorf("%s: got %d, want %d", name, got, want)
}
}
func TestWithHelper(t *testing.T) {
assertResult(t, Add(2, 3), 5, "Add(2, 3)")
assertResult(t, Subtract(5, 2), 3, "Subtract(5, 2)")
}
Code language: JavaScript (javascript)
Testing Complex Types
Here’s how to test more complex functionality:
type Calculator struct {
precision int
}
func (c *Calculator) DivideWithPrecision(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
result := a / b
return math.Round(result*math.Pow10(c.precision)) / math.Pow10(c.precision), nil
}
Code language: JavaScript (javascript)
Test complex operations:
func TestDivideWithPrecision(t *testing.T) {
tests := []struct {
name string
a, b float64
precision int
expected float64
expectedError bool
}{
{"simple division", 10, 2, 2, 5.00, false},
{"division with rounding", 10, 3, 2, 3.33, false},
{"division by zero", 10, 0, 2, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
calc := &Calculator{precision: tt.precision}
result, err := calc.DivideWithPrecision(tt.a, tt.b)
if tt.expectedError {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if result != tt.expected {
t.Errorf("got %f, want %f", result, tt.expected)
}
})
}
}
Code language: JavaScript (javascript)
Coverage and Performance
Check test coverage:
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Measure performance with benchmarks:
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
func BenchmarkDivideWithPrecision(b *testing.B) {
calc := &Calculator{precision: 2}
for i := 0; i < b.N; i++ {
calc.DivideWithPrecision(10, 3)
}
}
Testing External Dependencies
Use interfaces and mocks:
type DataStore interface {
Save(data string) error
Load() (string, error)
}
type MockDataStore struct {
savedData string
err error
}
func (m *MockDataStore) Save(data string) error {
if m.err != nil {
return m.err
}
m.savedData = data
return nil
}
func (m *MockDataStore) Load() (string, error) {
if m.err != nil {
return "", m.err
}
return m.savedData, nil
}
Code language: PHP (php)
Testing HTTP Handlers
Test web endpoints:
func TestHTTPHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, World!")
})
handler.ServeHTTP(w, req)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
if string(body) != "Hello, World!" {
t.Errorf("got %q, want %q", string(body), "Hello, World!")
}
}
Code language: JavaScript (javascript)
Test-Driven Development Tips
- Write tests first
- Keep tests focused
- Use clear test names
- Make tests independent
- Avoid test dependencies
- Update tests with code changes
Wrapping Up
Good tests make Go applications reliable and easier to maintain. Start with simple unit tests, then add more complex patterns as needed. Put these testing methods to work in your next project or add them to existing code.
Need more Go tips? Check our Go Error Handling guide to complement your testing skills.