
go-testing-patterns
by hoangtran1411
SKILL.md
name: Go Testing Patterns description: Comprehensive testing patterns for Go applications including table-driven tests, mocking interfaces, build tags, and CI integration.
Go Testing Patterns
This skill provides professional testing patterns for Go applications, focusing on maintainability, coverage, and CI integration.
When to Use
- Writing unit tests for Go packages
- Mocking external dependencies (databases, APIs, shells)
- Setting up CI/CD pipelines with test coverage
- Creating integration tests with build tags
Core Patterns
1. Table-Driven Tests
The preferred pattern for testing multiple scenarios:
func TestValidateInput(t *testing.T) {
tests := []struct {
name string
input string
want bool
wantErr bool
}{
{
name: "valid input",
input: "hello",
want: true,
},
{
name: "empty input",
input: "",
wantErr: true,
},
{
name: "special characters",
input: "hello-world_123",
want: true,
},
{
name: "invalid characters",
input: "hello;drop",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ValidateInput(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateInput() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ValidateInput() = %v, want %v", got, tt.want)
}
})
}
}
2. Interface-Based Mocking
Define interfaces for external dependencies to enable mocking:
// In production code
type DataStore interface {
Get(id string) (*Item, error)
Save(item *Item) error
}
type Service struct {
store DataStore
}
// In test code
type MockDataStore struct {
GetFunc func(id string) (*Item, error)
SaveFunc func(item *Item) error
}
func (m *MockDataStore) Get(id string) (*Item, error) {
if m.GetFunc != nil {
return m.GetFunc(id)
}
return nil, nil
}
func (m *MockDataStore) Save(item *Item) error {
if m.SaveFunc != nil {
return m.SaveFunc(item)
}
return nil
}
// Usage in tests
func TestService_GetItem(t *testing.T) {
mock := &MockDataStore{
GetFunc: func(id string) (*Item, error) {
return &Item{ID: id, Name: "Test"}, nil
},
}
service := &Service{store: mock}
item, err := service.GetItem("123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if item.Name != "Test" {
t.Errorf("expected Name 'Test', got '%s'", item.Name)
}
}
3. Test Helpers
Create reusable test helpers:
// testhelpers_test.go
// Helper to create test items
func makeTestItem(t *testing.T, name string) *Item {
t.Helper()
return &Item{
ID: uuid.New().String(),
Name: name,
CreatedAt: time.Now(),
}
}
// Helper for comparing errors
func assertError(t *testing.T, got, want error) {
t.Helper()
if got == nil && want != nil {
t.Errorf("expected error %v, got nil", want)
return
}
if got != nil && want == nil {
t.Errorf("unexpected error: %v", got)
return
}
if got != nil && want != nil && !errors.Is(got, want) {
t.Errorf("expected error %v, got %v", want, got)
}
}
// Helper for cleanup
func setupTestEnv(t *testing.T) func() {
t.Helper()
// Setup...
return func() {
// Cleanup...
}
}
4. Build Tags for Integration Tests
Separate unit tests from integration tests:
//go:build integration
// +build integration
package mypackage
import (
"testing"
)
func TestIntegration_RealDatabase(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
// Real database tests here
}
Run with: go test -tags=integration ./...
5. Windows-Specific Tests
For Windows-only functionality:
//go:build windows
// +build windows
package hyperv
import (
"testing"
)
func TestHyperV_ListVMs(t *testing.T) {
// This test only runs on Windows
}
6. Test Main for Setup/Teardown
func TestMain(m *testing.M) {
// Setup before all tests
setup()
// Run all tests
code := m.Run()
// Teardown after all tests
teardown()
os.Exit(code)
}
func setup() {
// Initialize resources
}
func teardown() {
// Clean up resources
}
Coverage Commands
# Run tests with coverage
go test -v -cover ./...
# Generate coverage profile
go test -coverprofile=coverage.out ./...
# View coverage in browser
go tool cover -html=coverage.out
# Get coverage percentage
go test -cover ./... | grep coverage
# Coverage for specific package with detailed output
go test -v -coverprofile=coverage.out -covermode=atomic ./mypackage/...
Makefile Integration
.PHONY: test test-coverage test-integration
test: ## Run unit tests
go test -v ./...
test-coverage: ## Run tests with coverage report
go test -v -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
@echo "Coverage report: coverage.html"
test-integration: ## Run integration tests
go test -v -tags=integration ./...
test-short: ## Run quick tests only
go test -v -short ./...
bench: ## Run benchmarks
go test -bench=. -benchmem ./...
GitHub Actions CI
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: windows-latest # or ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run tests
run: go test -v ./...
- name: Run tests with coverage
run: go test -v -coverprofile=coverage.out -covermode=atomic ./...
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
files: ./coverage.out
fail_ci_if_error: false
Best Practices
-
Name tests descriptively:
TestFunction_Scenario_ExpectedResult -
Use
t.Helper()in helper functions for better error reporting -
Avoid test pollution: Each test should be independent
-
Use
t.Parallel()for parallel execution when safe:func TestParallel(t *testing.T) { t.Parallel() // Test code } -
Keep tests fast: Unit tests should run in milliseconds
-
Test error paths: Don't just test happy paths
-
Use meaningful assertions:
// ❌ Bad if result != expected { t.Fail() } // ✅ Good if result != expected { t.Errorf("got %v, want %v", result, expected) } -
Test behavior, not implementation: Focus on what the function does, not how
Common Patterns for Mocking
Mock with Call Tracking
type MockService struct {
Calls []string
ReturnVal interface{}
ReturnErr error
}
func (m *MockService) DoSomething(id string) error {
m.Calls = append(m.Calls, "DoSomething:"+id)
return m.ReturnErr
}
// In test
func TestTrackCalls(t *testing.T) {
mock := &MockService{}
// ... run code ...
if len(mock.Calls) != 2 {
t.Errorf("expected 2 calls, got %d", len(mock.Calls))
}
}
Mock with Sequence of Returns
type SequenceMock struct {
Returns []interface{}
Index int
}
func (m *SequenceMock) Next() interface{} {
if m.Index >= len(m.Returns) {
return nil
}
val := m.Returns[m.Index]
m.Index++
return val
}
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です