
go-test
by d3fvxl
SKILL.md
name: go-test description: > Go testing patterns and best practices. Load when writing or running Go tests. Triggers: running tests, writing test files, test coverage, benchmarks, integration tests, table-driven tests, mocking, fixtures, or asking about Go testing patterns. license: MIT compatibility: opencode
Go Testing
Run and write tests for Go projects following best practices.
When This Skill MUST Be Used
ALWAYS invoke this skill when the user's request involves ANY of these:
- Running
go testor asking to run tests - Writing or modifying
*_test.gofiles - Setting up integration tests with databases
- Test coverage analysis
- Benchmarking Go code
- Mocking or test fixtures
- Table-driven tests
- Race condition detection
- Test structure (GIVEN-WHEN-THEN)
If you're about to run or write Go tests, STOP and use this skill first.
Critical Safety Rules
NEVER:
- Run integration tests without checking for required env vars
- Skip the
-raceflag in CI/CD pipelines - Leave test containers running after tests
- Commit code with failing tests
- Use
require/Fatalinside goroutines (causes panic) - Share mocks, loggers, or DB connections between parallel tests
ALWAYS:
- Use
t.Parallel()for independent tests AND subtests - Clean up test resources with
t.Cleanup() - Use build tags to separate unit and integration tests
- Check test coverage for new code
- Mark helper functions with
t.Helper()
Quick Reference
| Task | Command |
|---|---|
| Run all tests | go test ./... |
| Verbose output | go test -v ./... |
| With coverage | go test -cover ./... |
| With race detection | go test -race ./... |
| Run specific test | go test -run TestName ./... |
| Run benchmarks | go test -bench=. ./... |
| Force re-run | go test -count=1 ./... |
Test Structure: GIVEN-WHEN-THEN
- GIVEN: Setup and preconditions (use
require- stop if setup fails) - WHEN: Single action being tested (use
require- stop if action fails) - THEN: Assertions on results (use
assert- see all failures)
Critical: Only ONE WHEN section per test. Multiple behaviors = multiple tests.
require vs assert
| Phase | Use | Rationale |
|---|---|---|
| GIVEN | require | Stop if setup fails |
| WHEN | require | Stop if action fails |
| THEN | assert | See all failures |
| Goroutines | assert | NEVER use require (causes panic) |
Goroutine Warning: Never use require/Fatal inside goroutines, HTTP handlers, or callbacks - causes "FailNow called from non-test goroutine" panic.
Parallel Execution
- Always use
t.Parallel()in tests AND subtests - Each test creates its own fixture - no shared state
- Subtests must NOT access parent test variables
Forbidden Sharing:
- Loggers, database connections, singletons
- File handles, network ports
- Mock expectations defined in parent scope
Acceptable Sharing:
- Read-only table data in table-driven tests
- Global constants, types, and functions
File Naming
| Type | File Suffix | Package Declaration | Access |
|---|---|---|---|
| Public tests | *_test.go | package foo_test | Exported only (preferred) |
| Private tests | *_internal_test.go | package foo | All members |
| Integration | *_integration_test.go | //go:build integration | Real deps |
Default: Use public tests. Add private tests only for internal invariants or combinatorial explosion.
Test Naming
- Functions:
Test<Type>_<Method>(e.g.,TestUserService_CreateUser) - Subtests: Short, descriptive (e.g.,
"with valid input","returns error")
Table-Driven Tests
When to Use
- Simple data variations (primitives only)
- Boundary conditions and validation
- 3-4 fields maximum in table struct
The Simplicity Test
"Can I understand what each test case does by looking at one line in the table?"
If NO, use separate tests instead.
Anti-patterns (Never Put in Tables)
| Anti-pattern | Why Bad |
|---|---|
| Mock expectations | Behavior, not data |
| Boolean flags | Creates different test logic |
| Setup/assertion functions | Hides complexity |
| Auto-generated templates | Generic, not thoughtful |
Fixture Pattern
type fixture struct {
ctx context.Context
mockRepo *MockRepository
sut *Service
}
func newFixture(t *testing.T) *fixture {
t.Helper()
ctrl := gomock.NewController(t)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
t.Cleanup(cancel)
mockRepo := NewMockRepository(ctrl)
return &fixture{
ctx: ctx,
mockRepo: mockRepo,
sut: NewService(mockRepo),
}
}
Mocking
- Define interfaces in
interfaces.go - Add
//go:generate mockgendirective - Store mocks in
mock_test.gowithpackage foo_test gomock.Any()when values don't mattergomock.Eq()for exact matching in happy path- Create new mock controller per test/subtest
Must Constructor Pattern
For test helpers that panic on error (never in production):
// Production: returns error
func NewCategoryID(value string) (CategoryID, error)
// Tests only: panics on error
func MustCategoryID(value string) CategoryID {
id, err := NewCategoryID(value)
if err != nil {
panic(err)
}
return id
}
Standard library examples: regexp.MustCompile, template.Must
Deterministic Tests
Inject all nondeterminism:
| Source | Solution |
|---|---|
time.Now() | Clock interface |
uuid.New() | UUIDGenerator interface |
| Random numbers | Injectable generator |
Contract Testing (Marshal/Unmarshal)
- Use raw JSON strings as the "contract"
- Don't use internal types for both encode and decode
- Use
testify.JSONEq()for comparison - Store JSON contracts in
testdata/directory
Testing Conversion Layers
What to test in HTTP/gRPC handlers:
- Request unmarshaling and argument passing
- Error translation (domain errors -> protocol errors)
- Response marshaling
Pattern: Mock domain layer, verify exact arguments, verify error codes.
Coverage
| Type | Goal | Build Tag |
|---|---|---|
| Unit tests | 100% (all deps mocked) | None |
| Integration tests | As appropriate | integration |
Running Tests
Basic Commands
# Unit tests only
go test ./...
# Unit tests with verbose output
go test -v ./...
# Unit tests with coverage
go test -cover ./...
# Unit tests with coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Running Single Tests
# Run specific test by name
go test -v -run TestOrderRepository ./internal/infra/mysqlrepo/...
# Run tests matching pattern
go test -v -run "TestOrder.*" ./...
# Run specific subtest
go test -v -run "TestOrderRepository/creates_order" ./...
Integration Tests
Integration tests typically use build tags to separate from unit tests:
# Run integration tests (requires database)
go test -tags=integration -v ./...
# Run integration tests for specific package
go test -tags=integration -v ./internal/infra/mysqlrepo/...
# With database DSN
TEST_MYSQL_DSN="root:pass@tcp(localhost:3306)/testdb?parseTime=true" \
go test -tags=integration -v ./...
Docker-based Database for Tests
# Start MySQL container
docker run -d --name test-mysql \
-e MYSQL_ROOT_PASSWORD=pass \
-e MYSQL_DATABASE=testdb \
-p 3306:3306 \
mysql:8
# Wait for MySQL to be ready
until docker exec test-mysql mysqladmin ping -h localhost --silent; do
sleep 1
done
# Run integration tests
TEST_MYSQL_DSN="root:pass@tcp(localhost:3306)/testdb?parseTime=true" \
go test -tags=integration -v ./...
# Clean up
docker rm -f test-mysql
Integration Test Setup
//go:build integration
package mysqlrepo_test
import (
"os"
"testing"
)
func TestMain(m *testing.M) {
dsn := os.Getenv("TEST_MYSQL_DSN")
if dsn == "" {
// Skip integration tests if DSN not set
os.Exit(0)
}
// Setup database connection
// Run migrations if needed
code := m.Run()
// Cleanup
os.Exit(code)
}
Coverage
# Generate coverage profile
go test -coverprofile=coverage.out ./...
# View coverage in terminal
go tool cover -func=coverage.out
# View coverage in browser
go tool cover -html=coverage.out
# Coverage for specific packages
go test -coverprofile=coverage.out -coverpkg=./internal/... ./...
Race Detection
# Run tests with race detector
go test -race ./...
# Race detection with specific test
go test -race -run TestConcurrentAccess ./...
Benchmarks
# Run all benchmarks
go test -bench=. ./...
# Run specific benchmark
go test -bench=BenchmarkOrderCreation ./internal/domain/order/...
# Benchmarks with memory allocation stats
go test -bench=. -benchmem ./...
# Run benchmark multiple times
go test -bench=. -count=5 ./...
Test Caching
# Force re-run (disable cache)
go test -count=1 ./...
# Clean test cache
go clean -testcache
Makefile Targets
.PHONY: test test-unit test-integration test-coverage
test: test-unit
test-unit:
go test -v -race ./...
test-integration:
go test -tags=integration -v ./...
test-coverage:
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| Tests skipped | Missing env vars | Set TEST_MYSQL_DSN etc. |
| Connection refused | DB not running | Start container, wait for ready |
| Race detected | Concurrent access | Add mutex or redesign |
| Flaky tests | Shared state / timing | Use t.Parallel(), fix timing |
| Slow tests | No parallelization | Add t.Parallel() |
| Cache issues | Stale results | Use -count=1 or clean cache |
| "FailNow from non-test goroutine" | require in goroutine | Use assert instead |
Example Requests
| User Request | Action |
|---|---|
| "Run the tests" | go test -v ./... |
| "Run tests with coverage" | go test -cover ./... then go tool cover -html |
| "Run only integration tests" | go test -tags=integration -v ./... |
| "Check for race conditions" | go test -race ./... |
| "Run a specific test" | go test -v -run TestName ./path/... |
| "Benchmark this function" | go test -bench=BenchmarkName -benchmem ./... |
| "Why are tests slow?" | Check for missing t.Parallel(), use -p 1 for isolation |
| "Write a test for this function" | Use GIVEN-WHEN-THEN structure with fixture pattern |
| "Should I use table-driven tests?" | Only if simple data variations, 3-4 fields max |
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です