Install
$ agentstack add skill-els0r-skills-go-testing ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Go Testing
Every piece of logic must be tested.
Test Libraries
| Library | When to use | |---------|------------| | testify/require | Default — fails fast, clear messages | | testify/assert | Only when the test should continue after a failure | | google/go-cmp | Deep comparisons with custom cmp.Options | | gock | Mock HTTP calls — never hit real endpoints in tests | | testcontainers | Integration tests with external services (databases, queues) |
Favor require over assert. It fails immediately and prevents cascading noise.
Table-Driven Tests
The default pattern. Use for all new tests.
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
want bool
wantErr string // use sentinel errors when possible (see go-style)
}{
{"valid email", "user@example.com", true, ""},
{"empty email", "", false, "email cannot be empty"},
{"invalid format", "not-an-email", false, "invalid email format"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ValidateEmail(tt.email)
if tt.wantErr != "" {
require.ErrorContains(t, err, tt.wantErr)
return
}
require.NoError(t, err)
require.Equal(t, tt.want, got)
})
}
}
When testing sentinel errors, use require.ErrorIs(t, err, ErrNotFound) instead of string matching.
Test Helpers and Setup
- Mark helpers with
t.Helper()— no exceptions. Stack traces point to the calling test. - Use
t.Cleanup(fn)for teardown instead ofdefer— it runs even if the test callst.Fatal. - Arrange-act-assert structure. Every time.
func setupTestDB(t *testing.T) *DB {
t.Helper()
db, err := NewDB(":memory:")
require.NoError(t, err)
t.Cleanup(func() { db.Close() })
return db
}
Parallel Tests
Use t.Parallel() for independent subtests to speed up suites.
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// ...
})
}
Go 1.22+: loop variable is per-iteration scoped — tt := tt capture is no longer needed.
Mocking
Mock via interfaces. Define mocks in the test file, not production code. Keep them minimal.
type MockEmailSender struct {
Sent []Email
Err error // set to simulate failure
}
func (m *MockEmailSender) Send(to, subject, body string) error {
if m.Err != nil {
return m.Err
}
m.Sent = append(m.Sent, Email{to, subject, body})
return nil
}
Fuzzing
Use testing.F for input-discovery testing on parsers, validators, and serialization.
func FuzzParseConfig(f *testing.F) {
f.Add([]byte(`{"key": "value"}`))
f.Add([]byte(`{}`))
f.Fuzz(func(t *testing.T, data []byte) {
cfg, err := ParseConfig(data)
if err != nil {
return // invalid input is expected
}
// round-trip: marshal back and re-parse
out, err := cfg.Marshal()
require.NoError(t, err)
require.Equal(t, cfg, mustParse(t, out))
})
}
Test File Conventions
| Type | File Pattern | Notes | |------|-------------|-------| | Unit | *_test.go | Same package | | Integration | *_integration_test.go | Use //go:build integration build tag |
go test ./... # unit tests
go test -tags integration ./... # integration tests
go test -count=1 ./... # bypass test cache
Benchmarks
Write benchmarks for performance-sensitive code. Use b.Loop() (Go 1.24+) — prevents dead-code elimination automatically.
func BenchmarkProcessData(b *testing.B) {
data := generateTestPayload()
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
ProcessData(data)
}
}
For benchstat comparisons, sync.Pool, and the full optimization workflow, see the go-performance skill.
Race Detector
Always run tests with -race. Non-negotiable for goroutines, shared state, or channels.
go test -race ./...
Skipping Slow Tests
Use testing.Short() to gate expensive tests in local dev. CI runs without -short.
func TestExpensiveIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
// ...
}
Rules
- No test, no merge. Every exported function gets a test. Every bug fix gets a regression test.
requireoverassertunless you explicitly need the test to continue.errors.Isover string matching — use sentinel errors (seego-style).- No real endpoints in tests — mock HTTP with
gock, databases withtestcontainersor in-memory fakes. t.Helper()on every helper — no exceptions.-racein CI — always.- Table-driven by default — flat tests only for genuinely one-off cases.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: els0r
- Source: els0r/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.