Go Fuzz Testing: Automated Bug Discovery
Go's fuzz testing automatically generates strange inputs to crash your code, finding bugs you'd never think to test. It's ideal for stress-testing parsers or security-sensitive functions.
WHY IT EXISTS Manually writing unit tests for every possible malformed or malicious input is impossible. Fuzz testing was created to automate the discovery of edge-case bugs by feeding a program a vast quantity of generated inputs, aiming to find one that causes a crash or reveals a vulnerability.
THE MENTAL MODEL Think of Go's fuzzer as an army of intelligent monkeys at keyboards. They start by typing inputs you give them (the "seed corpus"). If an input reveals a new part of your code, the fuzzer focuses its efforts there, mutating that input to explore deeper. It's not just random; it's "coverage-guided," meaning it prioritizes inputs that increase code coverage, making it highly efficient at finding bugs.
HOW IT WORKS Fuzzing in Go has two modes. First, running go test executes your fuzz test like a standard unit test against a predefined "seed corpus" you provide with f.Add(). This ensures your baseline cases work. Second, running go test -fuzz=FuzzXxx starts the actual fuzzing engine. It takes the seed inputs, continuously mutates them, and feeds them to your fuzz target—a function you define with f.Fuzz(). If an input causes a panic, the fuzzer stops and saves the failing input to a file in testdata/fuzz for you to debug.
WHEN TO USE IT Use fuzzing on functions that parse complex or untrusted data, such as JSON/YAML decoders, image parsers, or network protocol handlers. Any function where malformed input could lead to a panic, infinite loop, or security exploit is a prime candidate. It's a powerful tool for hardening public-facing APIs and libraries.
WHEN NOT TO USE IT Avoid fuzzing functions that are slow, non-deterministic, or have external side effects like making network calls or database queries. The fuzzing engine runs the target function thousands or millions of times per second. Speed and determinism are critical for the fuzzer to work efficiently and for failures to be reproducible.
ONE CANONICAL EXAMPLE In a file named parser_test.go, you'd write a function func FuzzMyParser(f *testing.F). You might provide a valid starting point with f.Add("some-good-input"). Then, you define the target: f.Fuzz(func(t *testing.T, original string) { MyParser(original) }). The MyParser function is the code being tested. If any original string generated by the fuzzer causes MyParser to panic, the test fails and the input is saved.
Read the original → go.dev
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.