Question
I want to measure code coverage for Go unit tests. Is there a standard way to generate a coverage report in Go, and what tools or commands should I use?
Short Answer
By the end of this page, you will understand how test coverage works in Go, how to generate coverage reports with Go's built-in tools, how to read the results, and how coverage fits into real testing workflows.
Concept
Go includes built-in support for test coverage, so you usually do not need a separate third-party tool just to get started. Test coverage tells you which parts of your code were executed while your tests ran.
In Go, coverage is most commonly measured with the go test command and the -cover flags. This helps you answer questions like:
- Did my tests actually execute this function?
- Which files have low coverage?
- Are there branches or cases I forgot to test?
Coverage matters because it gives feedback about how much of your code your tests touch. That said, coverage is a signal, not proof of correctness. A test can execute a line without properly checking the result.
Go's coverage tooling is practical because it is:
- Built in to the language toolchain
- Easy to run locally
- Useful in CI pipelines
- Good for spotting untested areas quickly
The most common outputs are:
- A percentage shown in the terminal
- A coverage profile file
- An HTML report you can inspect visually
A simple workflow looks like this:
- Write unit tests in
*_test.gofiles - Run
go test -cover - Generate a coverage profile with
-coverprofile - View details with
go tool cover
This concept is important in real programming because teams often use coverage reports to maintain testing standards, catch untested code paths, and track quality over time.
Mental Model
Think of test coverage like walking through rooms in a building with a motion sensor.
- Your codebase is the building
- Each function or line is a room
- Your tests are the person walking through the building
- Coverage shows which rooms were entered
If a room was never entered, that part of the code was not tested at all. But entering a room does not mean you checked whether everything inside it works correctly. That is why coverage is useful, but not enough by itself.
Syntax and Examples
The basic Go command for coverage is:
go test -cover
This runs tests in the current package and prints a coverage percentage.
Example code
package mathutil
func Add(a, b int) int {
return a + b
}
func IsEven(n int) bool {
return n%2 == 0
}
package mathutil
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2, 3) = %d; want %d", got, want)
}
}
Run:
go test -cover
Possible output:
Step by Step Execution
Consider this code:
package discount
func PriceAfterDiscount(price int, discount int) int {
if discount <= 0 {
return price
}
return price - discount
}
And this test:
package discount
import "testing"
func TestPriceAfterDiscount(t *testing.T) {
got := PriceAfterDiscount(100, 20)
want := 80
if got != want {
t.Fatalf("got %d, want %d", got, want)
}
}
Now run:
go test -coverprofile=coverage.out
What happens step by step
- Go finds all test files ending in
*_test.go. - It builds an instrumented version of your code.
- Instrumented means Go adds tracking so it can record which statements run.
- Go runs
TestPriceAfterDiscount.
Real World Use Cases
Coverage reporting in Go is useful in many practical situations:
API handlers
You may want to confirm that tests execute:
- Success responses
- Validation failures
- Unauthorized access
- Internal error paths
Business logic
For pricing, permissions, or calculations, coverage helps reveal missing edge-case tests.
Data processing scripts
If a Go program parses files or transforms records, coverage can show whether tests hit:
- Empty input
- Invalid rows
- Normal valid input
- Large batches
Libraries and packages
If you publish a reusable Go package, coverage helps you see whether your public functions are actually tested.
CI pipelines
Teams often run commands like:
go test ./... -coverprofile=coverage.out
Then they may:
- Store the report as a build artifact
- Fail the build if coverage drops too far
- Track coverage trends over time
Refactoring safety
Before changing older code, developers often review coverage to understand whether enough tests exist to make refactoring safer.
Real Codebase Usage
In real Go projects, coverage is usually part of a broader testing workflow rather than a one-off command.
Common patterns developers use
Run coverage across the whole module
go test ./... -cover
This is common in monorepos and multi-package applications.
Generate a reusable profile in CI
go test ./... -coverprofile=coverage.out
The profile can then be analyzed or uploaded to reporting systems.
Inspect function-level gaps
go tool cover -func=coverage.out
Developers use this to identify exactly which functions are under-tested.
Use HTML reports during debugging
go tool cover -html=coverage.out
This helps visually inspect untested lines after writing or refactoring tests.
How coverage supports code patterns
Guard clauses and early returns
Functions often return early on invalid input. Coverage helps confirm that tests hit both:
- The early return path
- The normal path
Validation logic
If a function checks for missing fields, bad formats, or invalid ranges, coverage can reveal whether error cases are tested.
Common Mistakes
1. Thinking coverage means correctness
A high percentage does not guarantee your code is bug-free.
Broken idea:
func TestAdd(t *testing.T) {
Add(2, 3)
}
This may execute the function, but it does not assert anything meaningful.
Better:
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Fatalf("got %d, want %d", got, want)
}
}
2. Only running coverage in one package
If you run:
go test -cover
it only covers the current package. Beginners often expect the whole project to be included.
Use this for all packages:
go test ./... -cover
3. Ignoring edge cases
A function may appear covered even though important branches are missing.
Example:
Comparisons
| Approach | What it does | Best for | Notes |
|---|---|---|---|
go test -cover | Shows a quick coverage percentage | Fast local checks | Easy starting point |
go test -coverprofile=coverage.out | Saves detailed coverage data | CI and deeper analysis | Needed for reports |
go tool cover -func=coverage.out | Shows coverage by function | Finding weak spots | Good for review |
go tool cover -html=coverage.out | Opens a visual report | Exploring uncovered lines | Very beginner-friendly |
Coverage vs test quality
Cheat Sheet
# Run tests in current package with coverage
go test -cover
# Run tests in all packages with coverage
go test ./... -cover
# Generate a coverage profile
go test ./... -coverprofile=coverage.out
# Show per-function coverage
go tool cover -func=coverage.out
# Open HTML coverage report
go tool cover -html=coverage.out
Key points
- Go has built-in coverage tooling.
- Test files should end with
*_test.go. - Coverage shows executed statements during tests.
- Coverage is useful, but it does not prove correctness.
- Use
./...to test all packages in a module. - Use
-coverprofilewhen you want detailed reporting.
Good habits
- Test both happy paths and error paths.
- Add assertions, not just function calls.
- Use table-driven tests for multiple cases.
- Review uncovered lines after refactoring.
Common command sequence
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out
go tool cover -html=coverage.out
FAQ
Does Go have built-in code coverage tools?
Yes. Go includes coverage support through go test and go tool cover.
How do I generate a coverage report in Go?
Run:
go test -coverprofile=coverage.out
Then inspect it with:
go tool cover -func=coverage.out
or:
go tool cover -html=coverage.out
How do I measure coverage for all packages in a Go module?
Use:
go test ./... -cover
or generate a profile with:
go test ./... -coverprofile=coverage.out
What does coverage percentage mean in Go?
It means the percentage of statements that were executed while tests ran.
Is 100% test coverage necessary?
No. High coverage can be helpful, but meaningful assertions and good edge-case testing matter more.
Why does my coverage look low even though tests pass?
Your tests may only exercise a small part of the code, or they may miss branches, error paths, or other packages.
Mini Project
Description
Build a small Go package that calculates shipping costs and measure how much of it is covered by tests. This project demonstrates how coverage exposes missing cases and helps you improve your test suite.
Goal
Create a Go package with unit tests, generate a coverage profile, and identify covered and uncovered logic.
Requirements
- Create a Go package with one function that contains at least one conditional branch.
- Write unit tests for the normal case and at least one edge case.
- Run coverage with
-coverprofile. - Inspect the report using
go tool cover -funcorgo tool cover -html. - Update tests if you discover an uncovered path.
Keep learning
Related questions
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.