cmd/sgai/skel/.sgai/skills/coding-practices/go-testing-coverage/SKILL.md
Go testing patterns, coverage analysis, and best practices. When writing tests for Go code or analyzing test coverage
npx skillsauth add sandgardenhq/sgai go-testing-coverageInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Testing patterns and coverage analysis for Go projects.
For deeper information, fetch these URLs:
Tests live in *_test.go files alongside the code:
mypackage/
├── mycode.go
└── mycode_test.go
package mypackage
import "testing"
func TestFunctionName(t *testing.T) {
// Arrange
input := "test"
want := "expected"
// Act
got := FunctionName(input)
// Assert
if got != want {
t.Errorf("FunctionName(%q) = %q; want %q", input, got, want)
}
}
Follow Go convention: actual != expected, message matches order.
// GOOD
if got != want {
t.Errorf("FunctionName(%q) = %d; want %d", input, got, want)
}
// BAD - reversed
if want != got {
t.Errorf("expected %d, got %d", want, got)
}
Preferred pattern for multiple test cases:
func TestReverseRunes(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"simple", "Hello", "olleH"},
{"unicode", "Hello, 世界", "界世 ,olleH"},
{"empty", "", ""},
{"single", "a", "a"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ReverseRunes(tt.in)
if got != tt.want {
t.Errorf("ReverseRunes(%q) = %q; want %q", tt.in, got, tt.want)
}
})
}
}
func TestReverseRunes(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"simple", "Hello", "olleH"},
{"unicode", "Hello, 世界", "界世 ,olleH"},
}
for _, tt := range tests {
tt := tt // Capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := ReverseRunes(tt.in)
if got != tt.want {
t.Errorf("ReverseRunes(%q) = %q; want %q", tt.in, got, tt.want)
}
})
}
}
# Run all tests
go test ./...
# Run tests in current directory
go test
# Run specific test
go test -run TestFunctionName
# Run with subtests
go test -run TestReverseRunes/simple
# Verbose output
go test -v ./...
# Short mode (skip long tests)
go test -short ./...
# Run with race detector
go test -race ./...
# Run benchmarks
go test -bench=. ./...
# Run specific benchmark
go test -bench=BenchmarkFunctionName ./...
# With memory stats
go test -bench=. -benchmem ./...
# Coverage summary
go test -cover ./...
# Coverage with percentage
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
# Coverage HTML report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Count mode - how many times each statement executed
go test -covermode=count -coverprofile=coverage.out ./...
# Set mode - binary: was statement executed (default)
go test -covermode=set -coverprofile=coverage.out ./...
# Atomic mode - like count but thread-safe
go test -covermode=atomic -coverprofile=coverage.out ./...
# Build instrumented binary
go build -cover -o myapp ./cmd/myapp
# Set coverage output directory
GOCOVERDIR=./coverage ./myapp
# Merge coverage data
go tool covdata textfmt -i=./coverage -o coverage.out
func TestMain(m *testing.M) {
// Setup
setup()
// Run tests
code := m.Run()
// Teardown
teardown()
os.Exit(code)
}
func TestSomething(t *testing.T) {
helper := setupHelper(t)
defer helper.cleanup()
// Test code...
}
func setupHelper(t *testing.T) *TestHelper {
t.Helper() // Mark as helper for better error reporting
// Setup code...
return &TestHelper{}
}
func TestWithTempFile(t *testing.T) {
// Create temp directory (cleaned up automatically)
dir := t.TempDir()
// Create temp file
f, err := os.CreateTemp(dir, "test-*.txt")
if err != nil {
t.Fatal(err)
}
defer f.Close()
// Test code...
}
func BenchmarkReverseRunes(b *testing.B) {
input := "Hello, World!"
for i := 0; i < b.N; i++ {
ReverseRunes(input)
}
}
func BenchmarkReverseRunes(b *testing.B) {
input := "Hello, World!"
b.ResetTimer() // Reset after setup
for i := 0; i < b.N; i++ {
ReverseRunes(input)
}
}
func BenchmarkReverseRunes(b *testing.B) {
cases := []struct {
name string
in string
}{
{"short", "Hello"},
{"medium", "Hello, World! How are you today?"},
{"long", strings.Repeat("Hello, World!", 100)},
}
for _, tc := range cases {
b.Run(tc.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
ReverseRunes(tc.in)
}
})
}
}
func TestFunctionError(t *testing.T) {
_, err := FunctionThatMightFail(invalidInput)
if err == nil {
t.Fatal("expected error, got nil")
}
// Check specific error type
var myErr *MyError
if !errors.As(err, &myErr) {
t.Errorf("expected MyError, got %T", err)
}
}
func TestHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/path", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
if resp.StatusCode != http.StatusOK {
t.Errorf("status = %d; want %d", resp.StatusCode, http.StatusOK)
}
}
func TestWithContext(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
result, err := FunctionWithContext(ctx)
if err != nil {
t.Fatal(err)
}
// Assert result...
}
Use _test suffix for black-box testing:
// mypackage_test.go
package mypackage_test
import (
"testing"
"github.com/user/project/mypackage"
)
func TestPublicAPI(t *testing.T) {
// Can only access exported symbols
result := mypackage.PublicFunction()
// ...
}
| Command | Purpose |
|---------|---------|
| go test | Run tests |
| go test -v | Verbose output |
| go test -race | Race detection |
| go test -cover | Coverage summary |
| go test -coverprofile=c.out | Coverage profile |
| go tool cover -html=c.out | HTML report |
| go test -bench=. | Run benchmarks |
Documentation: https://go.dev/blog/testing-time
The slices package simplifies test assertions:
func TestSort(t *testing.T) {
got := MySort([]int{3, 1, 4, 1, 5})
want := []int{1, 1, 3, 4, 5}
// GOOD - use slices.Equal
if !slices.Equal(got, want) {
t.Errorf("MySort() = %v; want %v", got, want)
}
// Also useful: slices.Contains for membership tests
if !slices.Contains(got, 3) {
t.Error("expected result to contain 3")
}
}
Write type-safe test helpers using generics:
// Generic assertion helper
func assertEqual[T comparable](t *testing.T, got, want T) {
t.Helper()
if got != want {
t.Errorf("got %v; want %v", got, want)
}
}
// Generic slice assertion
func assertSliceEqual[T comparable](t *testing.T, got, want []T) {
t.Helper()
if !slices.Equal(got, want) {
t.Errorf("got %v; want %v", got, want)
}
}
For testing log/slog handlers:
import "testing/slogtest"
func TestHandler(t *testing.T) {
var buf bytes.Buffer
h := slog.NewJSONHandler(&buf, nil)
// slogtest verifies handler behavior
results := func() []map[string]any {
// Parse log output and return records
}
if err := slogtest.TestHandler(h, results); err != nil {
t.Error(err)
}
}
References:
Dead code in tests causes unnecessary review cycles. Before marking tests complete:
_ unless explicitly ignoring an errorsync/atomic when not needed)Example of bad code (from session):
triggerCount := &atomic.Int32{} // unused
originalRun := d.run // captured and discarded
Example of good code:
// Verify the debounce timer is still pending after 100ms
if !d.timer.Stop() {
<-d.timer.C // drain channel to avoid leak
}
slices.Equal insteaddocumentation
Start, stop, and steer agentic sessions in sgai workspaces. Use when you need to launch AI agent sessions, halt running sessions, or inject steering instructions to guide the agent mid-execution without stopping it.
development
Monitor sgai workspace status, events, progress, diffs, and workflow diagrams. Use when you need to observe what agents are doing, track progress, get the current state of all workspaces, subscribe to real-time updates via SSE, or inspect code changes.
development
Access agents, skills, and code snippets available in sgai workspaces. Use when you need to discover what agents are defined in a workspace, browse available skills, get skill instructions, find code snippets by language, or retrieve snippet content for a specific task.
data-ai
Handle agent questions and work gates in sgai workspaces. Use when an agent is blocked waiting for human input, when you need to respond to multi-choice questions, approve work gates, or provide free-text answers to agent queries.