Test Coverage in Go: Measuring and Improving Code Quality

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

How to Measure Test Coverage in Go Without Losing Your Mind

\n\n

You've written tests for your Go application. Feeling like a superhero? Rightly so. But there's one nuance: how much of your code is actually covered by tests? Without an answer to this question, your confidence is just reading tea leaves.

\n\n

The question "How to measure test coverage in Go" is one of the most popular on Stack Overflow (tens of thousands of views). And for good reason. Developers want not just to write tests, but to understand how effective they are. In this tutorial, we'll cover all the ways to measure test coverage in Go: from basic commands to advanced techniques with profiles and HTML reports.

\n\n

What is Test Coverage and Why Measure It?

\n\n

Test coverage is a metric that shows what percentage of your source code is executed during tests. It's expressed as a percentage. 100% coverage isn't always the Holy Grail, but aiming for high numbers is worthwhile.

\n\n

Why is this needed?

\n
    \n
  • Quality control: you see which parts of the code lack tests
  • \n
  • Confidence in refactoring: high coverage reduces the risk of breaking something
  • \n
  • CI/CD metrics: you can set a coverage threshold and block code with low coverage
  • \n
  • Documentation: tests are living documentation, and coverage shows how complete it is
  • \n
\n\n

The Catch: Coverage ≠ Test Quality

\n

Remember: 100% coverage does not guarantee the absence of bugs. You can write tests that simply call functions but don't verify anything. Coverage is a quantitative metric. Test quality is qualitative. Use both approaches.

\n\n

Basic Measurement: go test -cover

\n\n

Let's start with the simplest. Go has built-in coverage support right in the go test tool. Let's create a simple project and see how it works.

\n\n

Create the file math.go:

\n\n
package main\n\nfunc Add(a, b int) int {\n    return a + b\n}\n\nfunc Subtract(a, b int) int {\n    return a - b\n}\n\nfunc Multiply(a, b int) int {\n    return a * b\n}\n\nfunc Divide(a, b int) (int, error) {\n    if b == 0 {\n        return 0, nil // Bug: should be return 0, errors.New("division by zero")\n    }\n    return a / b, nil\n}
\n\n

And now the test (math_test.go):

\n\n
package main\n\nimport "testing"\n\nfunc TestAdd(t *testing.T) {\n    result := Add(2, 3)\n    if result != 5 {\n        t.Errorf("expected 5, got %d", result)\n    }\n}\n\nfunc TestSubtract(t *testing.T) {\n    result := Subtract(10, 4)\n    if result != 6 {\n        t.Errorf("expected 6, got %d", result)\n    }\n}\n\n// Intentionally not testing Multiply and Divide
\n\n

Now let's run the tests with the -cover flag:

\n\n
go test -cover
\n\n

The output will be something like:

\n\n
ok      example/math    0.002s  coverage: 50.0% of statements
\n\n

That's correct — 50% of the code is covered because Multiply and Divide are not tested. But this is just a total number. What if we want to see a detailed coverage profile?

\n\n

Coverage Profile: go test -coverprofile

\n\n

The -coverprofile flag saves detailed coverage information to a file:

\n\n
go test -coverprofile=coverage.out
\n\n

Now a file called coverage.out has appeared in the folder. Open it — it's a text format where each line shows whether a specific code instruction was executed. But reading it manually is tedious. Let's move on to visualization.

\n\n

HTML Report: go tool cover -html

\n\n

The most convenient way to analyze coverage is an HTML report. It highlights executed code in green and unexecuted code in red.

\n\n
go tool cover                

Blogs

Book Recommendations