Testing in Go
Go has built-in support for testing, making it easy to write, organize, and execute tests. The standard testing
package provides everything you need for unit testing, benchmarks, and examples. In this article, we’ll explore how to test Go code effectively, covering basic tests, table-driven testing, mocking, and best practices.
Basics of Testing in Go
Creating a Test File
Test files are named with the _test.go
suffix. If the source file is math.go
, the test file should be math_test.go
.
Writing a Basic Test
Each test function should start with Test
preffix and it should take a single parameter of type *testing.T
.
package main
import "testing"
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
Run the code in your browser (opens in a new tab), without installation.
Execute the following command for running the tests
go test ./...
Testing for Errors
Functions that return errors should be tested to ensure correct error handling.
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func TestDivide(t *testing.T) {
tests := []struct {
a, b int
expected int
expectError bool
}{
{10, 2, 5, false},
{10, 0, 0, true},
}
for _, tt := range tests {
result, err := Divide(tt.a, tt.b)
if tt.expectError {
if err == nil {
t.Errorf("expected error but got none")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("Divide(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
}
}
}
}
Mocking in Go
Mocking is useful for testing interactions with external systems like APIs or databases. Use interfaces to abstract dependencies and create mock implementations for testing.
type DataFetcher interface {
FetchData() (string, error)
}
func GetData(fetcher DataFetcher) (string, error) {
return fetcher.FetchData()
}
// Mock implementation
type MockFetcher struct{}
func (m MockFetcher) FetchData() (string, error) {
return "mocked data", nil
}
func TestGetData(t *testing.T) {
mock := MockFetcher{}
data, err := GetData(mock)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if data != "mocked data" {
t.Errorf("expected 'mocked data', got %s", data)
}
}
Run this code in your browser (opens in a new tab)
Benchmarks in Go
Benchmarking allows you to measure the performance of functions. Benchmark functions start with Benchmark
and take a *testing.B
parameter.
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
Run benchmarks:
go test -bench=.
Examples in Go
Go allows you to write example functions that serve as documentation and can also be executed as tests.
func ExampleAdd() {
fmt.Println(Add(2, 3))
// Output: 5
}
Run this code in your browser (opens in a new tab).
Test Coverage
Test coverage is a measure of how much of your code is executed when your tests run. In Go, the go test
command provides built-in support for measuring and reporting test coverage, making it easier to ensure your tests cover critical parts of your codebase.
To measure test coverage in Go, use the -cover
flag with the go test
command:
go test -cover ./...
Output
ok myapp 0.005s coverage: 85.0% of statements
This output indicates that 85% of your code statements were executed during the tests.
Generate Detailed Coverage Reports
For more detailed insights, use the -coverprofile
flag to generate a coverage report
go test -coverprofile=coverage.out ./...
The coverage.out
file contains detailed information about which lines were covered.
You can then visualize this report in the terminal or as an HTML file.
Terminal Visualization
go tool cover -func=coverage.out
Output
myapp/main.go:34: Add 100.0%
myapp/util.go:12: Multiply 85.7%
total: (statements) 90.0%
HTML Visualization
go tool cover -html=coverage.out
This opens an interactive HTML report in your default browser, highlighting covered and uncovered code lines.