go-testing-cli
Provides testing templates and patterns for Go CLI applications.
Install
mkdir -p .claude/skills/go-testing-cli && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13053" && unzip -o skill.zip -d .claude/skills/go-testing-cli && rm skill.zipInstalls to .claude/skills/go-testing-cli
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Testing patterns for Go CLI tools - command tests, golden tests, table-driven tests, filesystem tests, benchmarks, and fuzz tests. Use when writing or reviewing tests for Harvx.Key capabilities
- →Execute CLI commands with arguments.
- →Capture stdout/stderr/exit code.
- →Verify behavior of CLI commands.
- →Test flag validation and error messages.
- →Test `--help` output.
- →Use table-driven tests for multiple input/output combinations.
How it works
The skill provides patterns for testing Go CLI tools, including command execution, table-driven tests for input/output combinations, golden tests for stable output, and fuzz tests for security-critical code.
Inputs & outputs
When to use go-testing-cli
- →Test CLI commands
- →Write table-driven tests
- →Validate flag parsing
About this skill
Go CLI Testing Patterns
1 -- Command Tests
Execute CLI commands with arguments, capture stdout/stderr/exit code, and verify behavior.
Rules
- No network calls unless explicitly mocked.
- Test flag validation and error messages.
- Test
--helpoutput exists and is well-formatted. - Use
bytes.Bufferfor capturing output. - Test both success and error paths.
package cli_test
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/harvx/harvx/internal/cli"
)
func TestGenerateCommand_Success(t *testing.T) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := cli.NewRootCmd()
cmd.SetOut(stdout)
cmd.SetErr(stderr)
cmd.SetArgs([]string{"generate", "testdata/sample-repo"})
err := cmd.Execute()
require.NoError(t, err)
assert.Contains(t, stdout.String(), "files discovered")
assert.Empty(t, stderr.String())
}
func TestGenerateCommand_InvalidFlag(t *testing.T) {
stderr := new(bytes.Buffer)
cmd := cli.NewRootCmd()
cmd.SetErr(stderr)
cmd.SetArgs([]string{"generate", "--format", "invalid"})
err := cmd.Execute()
require.Error(t, err)
assert.Contains(t, err.Error(), "unknown format")
}
func TestGenerateCommand_HelpOutput(t *testing.T) {
stdout := new(bytes.Buffer)
cmd := cli.NewRootCmd()
cmd.SetOut(stdout)
cmd.SetArgs([]string{"generate", "--help"})
err := cmd.Execute()
require.NoError(t, err)
output := stdout.String()
assert.Contains(t, output, "Usage:")
assert.Contains(t, output, "Examples:")
assert.Contains(t, output, "--output-file")
assert.Contains(t, output, "--profile")
}
2 -- Table-Driven Tests
Rules
- Use for multiple input/output combinations.
- Name test cases so failures are immediately diagnosable.
- Split success and error paths into separate test functions.
- Use
testify/requirefor fatal assertions,testify/assertfor soft checks. - Mark independent subtests with
t.Parallel().
func TestTokenizer_Count_Success(t *testing.T) {
tok, err := tokenizer.New()
require.NoError(t, err)
tests := []struct {
name string
content string
want int
}{
{name: "empty string", content: "", want: 0},
{name: "single token", content: "hello", want: 1},
{name: "go function", content: "func main() {\n\tfmt.Println(\"hi\")\n}", want: 12},
{name: "unicode", content: "cafe\u0301", want: 3},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := tok.Count(tt.content)
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func TestTokenizer_Count_Errors(t *testing.T) {
tests := []struct {
name string
content string
wantErr string
}{
{name: "nil tokenizer panics recovered", content: "x", wantErr: "tokenizer not initialized"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var tok *tokenizer.Tokenizer // nil
_, err := tok.Count(tt.content)
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
3 -- Golden Tests
Use golden tests for JSON schema verification and stable text output.
Rules
- Provide
-updateflag to regenerate golden files. - Normalize timestamps, hashes, and absolute paths before comparison.
- Ensure deterministic ordering in output.
- Add golden tests when output format changes (catches regressions).
- Store golden files in
testdata/expected-output/.
package output_test
import (
"flag"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/harvx/harvx/internal/output"
)
var update = flag.Bool("update", false, "update golden files")
func TestMarkdownRenderer_Golden(t *testing.T) {
tests := []struct {
name string
input output.RenderInput
golden string
}{
{
name: "default profile",
input: loadTestInput(t, "default"),
golden: "testdata/expected-output/default.md",
},
{
name: "minimal profile",
input: loadTestInput(t, "minimal"),
golden: "testdata/expected-output/minimal.md",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := output.RenderMarkdown(tt.input)
require.NoError(t, err)
normalized := normalize(string(actual))
if *update {
err := os.WriteFile(tt.golden, []byte(normalized), 0644)
require.NoError(t, err, "failed to update golden file")
return
}
expected, err := os.ReadFile(tt.golden)
require.NoError(t, err, "golden file missing; run with -update to create")
assert.Equal(t, normalize(string(expected)), normalized)
})
}
}
// normalize removes non-deterministic content for stable comparisons.
func normalize(s string) string {
// Replace timestamps with placeholder
s = timestampRe.ReplaceAllString(s, "<TIMESTAMP>")
// Normalize path separators
s = strings.ReplaceAll(s, "\\", "/")
// Trim trailing whitespace per line
lines := strings.Split(s, "\n")
for i, line := range lines {
lines[i] = strings.TrimRight(line, " \t")
}
return strings.Join(lines, "\n")
}
4 -- Filesystem Tests
Rules
- Use
t.TempDir()for isolated directories (auto-cleaned). - Use
t.Helper()in all setup helper functions. - Create realistic test structures that mirror actual repo layouts.
- Test
.gitignorebehavior with real ignore files.
func setupTestRepo(t *testing.T) string {
t.Helper()
dir := t.TempDir()
// Source files
createFile(t, dir, "main.go", "package main\n\nfunc main() {}\n")
createFile(t, dir, "lib/util.go", "package lib\n\nfunc Helper() {}\n")
createFile(t, dir, "README.md", "# Test Project\n")
// Ignored paths
createFile(t, dir, ".gitignore", "*.log\n/dist/\nnode_modules/\n")
createFile(t, dir, "dist/bundle.js", "compiled code")
createFile(t, dir, "debug.log", "log content")
// Binary file
createFile(t, dir, "icon.png", string([]byte{0x89, 0x50, 0x4E, 0x47}))
return dir
}
func createFile(t *testing.T, base, rel, content string) {
t.Helper()
path := filepath.Join(base, rel)
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0755))
require.NoError(t, os.WriteFile(path, []byte(content), 0644))
}
func createDir(t *testing.T, base, rel string) {
t.Helper()
require.NoError(t, os.MkdirAll(filepath.Join(base, rel), 0755))
}
func TestWalker_IgnoresGitignorePatterns(t *testing.T) {
dir := setupTestRepo(t)
w, err := discovery.New(discovery.Options{Root: dir})
require.NoError(t, err)
files, err := w.Walk(context.Background())
require.NoError(t, err)
paths := extractPaths(files)
assert.Contains(t, paths, "main.go")
assert.Contains(t, paths, "lib/util.go")
assert.Contains(t, paths, "README.md")
assert.NotContains(t, paths, "dist/bundle.js")
assert.NotContains(t, paths, "debug.log")
}
func extractPaths(files []discovery.FileDescriptor) []string {
paths := make([]string, len(files))
for i, f := range files {
paths[i] = f.RelPath
}
return paths
}
5 -- Benchmark Tests
Add benchmarks for performance-critical paths: file walking, token counting, output rendering.
func BenchmarkWalker_LargeRepo(b *testing.B) {
dir := setupLargeBenchRepo(b) // creates 1000+ files
w, err := discovery.New(discovery.Options{Root: dir})
require.NoError(b, err)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := w.Walk(context.Background())
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkTokenizer_Count(b *testing.B) {
tok, _ := tokenizer.New()
content := strings.Repeat("func main() { fmt.Println(\"hello\") }\n", 1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tok.Count(content)
}
}
func setupLargeBenchRepo(b *testing.B) string {
b.Helper()
dir := b.TempDir()
for i := 0; i < 1000; i++ {
path := filepath.Join(dir, fmt.Sprintf("pkg%d/file.go", i))
os.MkdirAll(filepath.Dir(path), 0755)
os.WriteFile(path, []byte(fmt.Sprintf("package pkg%d\n", i)), 0644)
}
return dir
}
6 -- Fuzz Tests
Add fuzz tests for security-critical code: secret redaction, config parsing, input validation.
func FuzzRedactor_Redact(f *testing.F) {
// Seed corpus
f.Add("normal text without secrets")
f.Add("aws_secret_access_key = AKIAIOSFODNN7EXAMPLE")
f.Add("password: hunter2\ntoken: ghp_abc123")
f.Add(strings.Repeat("A", 10000))
f.Add("")
redactor := security.NewRedactor(security.DefaultPatterns())
f.Fuzz(func(t *testing.T, input string) {
output := redactor.Redact(input)
// Invariants that must always hold:
// 1. Output is never longer than input + redaction markers
// 2. No panic
// 3. Known patterns are redacted
if len(output) > len(input)*2+100 {
t.Errorf("output unexpectedly large: input=%d output=%d", len(input), len(output))
}
})
}
7 -- Test Execution Cheatsheet
go test ./... # All tests
go test ./internal/cli/... # Specific package
go test -v -run TestGenerate ./... # Specific test
go test -race ./... # Race detector
go test -count=1 ./... # No cache
go test -run TestGolden -update ./... # Update
---
*Content truncated.*
When not to use it
- →When network calls are not explicitly mocked.
- →When testing non-CLI Go components.
- →When golden files are not stored in `testdata/expected-output/`.
Limitations
- →Command tests should not make network calls unless explicitly mocked.
- →Golden tests require a `-update` flag to regenerate golden files.
- →Fuzz tests are for security-critical code like secret redaction or input validation.
How it compares
This skill offers structured testing patterns for Go CLI tools, including specific rules for command, table-driven, golden, filesystem, benchmark, and fuzz tests, providing a complete and standardized approach compared to ad-hoc testin
Compared to similar skills
go-testing-cli side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| go-testing-cli (this skill) | 0 | 5mo | Review | Intermediate |
| go-dev-guidelines | 14 | 9mo | No flags | Intermediate |
| code-formatting | 1 | 2mo | No flags | Beginner |
| tidb-test-guidelines | 2 | 27d | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
go-dev-guidelines
jumppad-labs
This skill should be used when writing, refactoring, or testing Go code. It provides idiomatic Go development patterns, TDD-based workflows, project structure conventions, and testing best practices using testify/require and mockery. Activate this skill when creating new Go features, services, packages, tests, or when setting up new Go projects.
code-formatting
openshift
MANDATORY: When writing Go tests, you MUST use 'When...it should...' format for ALL test names. When writing any Go code, you MUST remind user to run 'make lint-fix' and 'make verify'. These are non-negotiable HyperShift requirements.
tidb-test-guidelines
pingcap
Decide where to place TiDB tests and how to write them (basic structure, naming, testdata usage). Use when asked about test locations, writing conventions, shard_count limits, casetest categorization, or when reviewing test changes in code review.
go-table-driven-tests
Xe
Write Go table-driven tests following Go community best practices and this repository's conventions. Use when writing or refactoring Go tests, especially when you notice repeated test patterns or copy-pasted test code.
run-tests
pgschema
Run pgschema automated tests (go test) to validate diff logic, plan generation, and dump functionality using test fixtures
lsp-hover-testing
MadAppGang
Automated LSP hover validation for Dingo transpiler. Use when testing hover functionality, validating position mappings, checking for hover drift, or debugging LSP issues after sourcemap changes.