Code Coverage Testing in Go: From a "Green Checkmark" to Real Quality
\n\nIn the world of Go development, the phrase "I have 95% coverage" sounds impressive. But often behind this number lies emptiness: tests only check the happy path, while edge cases and errors are left behind. In this article, we'll figure out how to measure code coverage testing in Go for real, which metrics truly matter, and which tools will help turn testing from a formality into a powerful quality control tool.
\n\nWe won't just paraphrase the documentation. Instead — live examples, pitfalls, and practical advice for those who write Go and want to sleep soundly.
\n\nWhy is just 80% bad?
\n\nLet's say you have a function:
\n\nfunc ProcessOrder(amount float64, discount bool) (float64, error) {\n if amount <= 0 {\n return 0, errors.New("amount must be positive")\n }\n if discount {\n amount *= 0.9\n }\n return amount, nil\n}\n\nYou wrote one test: ProcessOrder(100, true) → 90, nil. Coverage? 100% of lines. But you didn't check the case with amount <= 0 and the case without a discount. Formally — excellent coverage. In reality — bugs are guaranteed. This is why code coverage metrics in Go need to be considered in conjunction with other indicators.
Coverage Tools: From Simple to Complex
\n\n1. Built-in go test -cover
\n\nThe simplest way to find out code coverage testing in Go is the -cover flag:
go test -cover ./...\n\nOutput:
\nok myproject/orders 0.234s coverage: 72.3% of statements\n\nBut this is just a general number. To see which lines are not covered, use -coverprofile:
go test -coverprofile=coverage.out ./...\ngo tool cover -html=coverage.out\n\nA browser will open with highlighting: green — covered, red — not. This is the first step to understanding Go coverage metrics.
\n\n2. go tool cover: modes and nuances
\n\ngo tool cover supports three modes:
- \n
- set — whether a line was executed at least once (default) \n
- count — how many times each line was executed \n
- atomic — count, but with atomic counters for parallel tests \n
For Golang test coverage analysis in multithreaded code, use atomic, otherwise you risk getting incorrect counters due to race conditions.
\n\ngo test -covermode=atomic -coverprofile=coverage.out ./...\n\n3. Race detector — hidden coverage
\n\nData races are one of the main problems in Go code. Even if line coverage is 100%, the race detector can find errors that tests missed. Run tests with the -race flag:
go test -race -cover ./...\n\nImportant: the race detector slows down tests by 2-20 times, but for critical modules this is a mandatory practice. Include it in CI for key packages.
\n\nMetrics That Really Matter
\n\nThe percentage of line coverage is just the tip of the iceberg. Here's what you need to track:
\n\n1. Branch coverage
\n\nThe standard go test -cover counts lines, not branches. For code coverage testing in Go at the branch level, use gocov or go-test-coverage. Example:
go get github.com/axw/gocov/gocov\ngocov test ./... | gocov report\n\nYou will see not only lines but also the number of uncovered conditions. For example, for a function with two if statements — 4 branches, of which 2 are covered.
\n\n2. Mutation testing
\n\nThe most honest way to evaluate test quality is