Skip to content
Back to skills

Golang Testing

ASecurity

Use when asked to benchmark Go code, compare Go implementations for performance, add a fuzz test, or investigate allocations on a hot path - covers go test -bench/-benchmem, sub-benchmarks, benchstat for noise-resistant comparison, and native fuzzing (testing.F). For basic table-driven unit tests use go-service-idioms or test-driven-development instead; this skill is specifically about benchmark-driven optimization and property-style fuzz testing.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added October 16, 2026
developmentrustgobashtestingperformance

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 16, 2026

npx -y skills add shennawardana23/skillme --skill golang-testing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Golang Testing?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Golang Testing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/shennawardana23-golang-testing/badge)](https://www.skillsdirectory.com/skills/shennawardana23-golang-testing)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: golang-testing
description: Use when asked to benchmark Go code, compare Go implementations for performance, add a fuzz test, or investigate allocations on a hot path - covers go test -bench/-benchmem, sub-benchmarks, benchstat for noise-resistant comparison, and native fuzzing (testing.F). For basic table-driven unit tests use go-service-idioms or test-driven-development instead; this skill is specifically about benchmark-driven optimization and property-style fuzz testing.
license: Apache-2.0
metadata:
  version: "0.1.0"
---

# Go Benchmark-Driven Optimization and Fuzzing

Table-driven unit tests answer "is this correct." This skill answers two
different questions: "is this fast enough, and did my change actually help"
(benchmarks), and "does this hold for inputs I didn't think to write a test
case for" (fuzzing). Reach for `go-service-idioms` or
`test-driven-development` for ordinary correctness tests first — only bring
in benchmarks once a performance question is on the table, and fuzzing once
a function parses or validates untrusted/varied input.

## Writing a benchmark

```go
func BenchmarkProcess(b *testing.B) {
    data := generateTestData(1000)
    b.ResetTimer() // exclude setup cost above from the measured loop
    for i := 0; i < b.N; i++ {
        Process(data)
    }
}
```

Run with `-benchmem` always — throughput (`ns/op`) without allocation counts
(`B/op`, `allocs/op`) hides regressions where a change got faster per-op but
started allocating more, which shows up as GC pressure under real load:

```bash
go test -bench=BenchmarkProcess -benchmem ./...
# BenchmarkProcess-8   10000   105234 ns/op   4096 B/op   10 allocs/op
```

## Sub-benchmarks to compare alternatives directly

Use `b.Run` to benchmark several implementations side by side in one
invocation, so the comparison runs under identical conditions:

```go
func BenchmarkJoin(b *testing.B) {
    parts := []string{"hello", "world", "foo", "bar", "baz"}

    b.Run("plus_concat", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            var s string
            for _, p := range parts {
                s += p
            }
            _ = s
        }
    })

    b.Run("strings_builder", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            var sb strings.Builder
            for _, p := range parts {
                sb.WriteString(p)
            }
            _ = sb.String()
        }
    })
}
```

This is how a claim like "`strings.Builder` is faster than `+=` in a loop"
(see `golang-patterns`) gets verified rather than taken on faith — run both,
read the numbers, don't guess.

## benchstat: comparing runs without fooling yourself

A single benchmark run is noisy — CPU frequency scaling, other processes,
and GC timing all shift `ns/op` run to run. Never compare two single runs
by eye and declare a winner; use
[`golang.org/x/perf/cmd/benchstat`](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat)
to get a statistical comparison with confidence:

```bash
go test -bench=. -benchmem -count=10 ./... > old.txt
# make the change
go test -bench=. -benchmem -count=10 ./... > new.txt
benchstat old.txt new.txt
```

`-count=10` (or higher) is required — `benchstat` needs multiple samples per
side to compute variance; feeding it single-run output gives a meaningless
delta with no confidence indication.

## Native fuzzing (Go 1.18+)

Fuzzing generates inputs the developer didn't think to write by hand,
seeded from a small corpus, looking for panics, crashes, or a property
violation you assert explicitly inside `f.Fuzz`.

```go
func FuzzParseJSON(f *testing.F) {
    f.Add(`{"name": "test"}`) // seed corpus — real examples to start from
    f.Add(`[]`)
    f.Add(`""`)

    f.Fuzz(func(t *testing.T, input string) {
        var result map[string]any
        if err := json.Unmarshal([]byte(input), &result); err != nil {
            return // invalid JSON is an expected outcome, not a failure
        }
        // Property: anything that unmarshals successfully must re-marshal.
        if _, err := json.Marshal(result); err != nil {
            t.Errorf("Marshal failed after successful Unmarshal: %v", err)
        }
    })
}
```

```bash
go test -fuzz=FuzzParseJSON -fuzztime=30s
```

Always seed with `f.Add` using real, representative examples — an unseeded
fuzz target wastes early cycles rediscovering the input format from
scratch instead of exploring edge cases around valid input.

Fuzzing asserts a **property** (parse-then-marshal round-trips, a compare
function is antisymmetric, a parser never panics), not a specific expected
output — there's no table of expected values because the inputs aren't
enumerated by the developer. Any input that makes the fuzz function panic
or fail its assertion is automatically saved under `testdata/fuzz/<name>/`
as a permanent regression test.

## When to reach for which

| Question | Tool |
|---|---|
| Is the output correct for this input? | table-driven test (`go-service-idioms`) |
| Is it fast enough, and did my change help? | `go test -bench -benchmem` + `benchstat` |
| Does a property hold across inputs I didn't write by hand? | `testing.F` fuzz test |

## Gotchas

- `b.N` is chosen by the testing framework, not the author — it scales up
  automatically until the benchmark runs long enough to measure reliably.
  Never hardcode a loop count instead of using `b.N`, and always call
  `b.ResetTimer()` after any per-benchmark setup so setup cost isn't
  counted in the measured `ns/op`.
- A benchmark that never uses its result (`process(data)` with the return
  value discarded) risks the compiler optimizing the call away entirely
  under future compiler versions — assign to a package-level variable or
  use `testing.B.Elapsed`-style patterns defensively if the result is
  otherwise unused, so the benchmark keeps measuring real work.
- Corpus files under `testdata/fuzz/` are regression tests, not scratch
  files — commit them; deleting a saved failing corpus entry after "fixing"
  the bug removes the proof the fix actually holds against that input.
- `go test -fuzz=Name` runs **only** the named fuzz target and ignores
  everything else in the package during the fuzzing phase — run the full
  `go test ./...` separately to confirm nothing else regressed.

## Real-world grounding

Go's native fuzzing (`testing.F`, shipped in Go 1.18) evolved directly from
Dmitry Vyukov's external `go-fuzz` project, which found real bugs in the Go
standard library itself (`image/png`, `encoding/json`, `compress/flate`)
years before fuzzing was built into `go test` — the tool exists because
hand-written table-driven cases systematically miss the malformed,
adversarial inputs that fuzzing generates automatically.

Files in this skill

  • SKILL.md6.6 KB
  • evals/evals.json3.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.