DEV Community

Quame Jnr
Quame Jnr

Posted on

Go Test Coverage: Visualizing Profiles

Table of Content

Introduction

Go has incredible tooling out of the box. It has tools for testing, benchmarking, profiling etc. I want to discuss an interesting one that impressed me: go test coverage and the different visualization options.

Go Tests

To help us understand these concepts, we will be writing some functions to do basic calculations.

  1. Let's first write a function that adds two numbers
func Add(num1 int, num2 int) int {
    return num1 + num2
}
Enter fullscreen mode Exit fullscreen mode
  1. Let's write some tests for it
import "testing"

func TestAddition(t *testing.T) {
    got := Add(1, 3)
    want := 4
    if got != want {
        t.Errorf("wanted %d got %d", want, got)
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. Let's run our tests to see if everything is ok
go test ./...
Enter fullscreen mode Exit fullscreen mode
ok      github.com/quamejnr/go-cover    0.687s
Enter fullscreen mode Exit fullscreen mode

Everything looks good.
We can also see how much of our code written is covered by tests and this is where the excitement begins.

Go Tests Coverage

To see the test coverage, we can add a -cover flag to our go test command.

go test ./... -cover
Enter fullscreen mode Exit fullscreen mode
ok      github.com/quamejnr/go-cover    (cached)        coverage: 100.0% of statements
Enter fullscreen mode Exit fullscreen mode

Currently we are 100% covered as we have written tests for all our code.
Now let's add a division function and see what happens.

func Div(num int, denom int) int {
  if denom == 0 {
    panic("invalid division error")
  }
    return num / denom
}
Enter fullscreen mode Exit fullscreen mode

We can rerun our test coverage command and see our response.

go test ./... -cover
Enter fullscreen mode Exit fullscreen mode
ok      github.com/quamejnr/go-cover    (cached)        coverage: 50.0% of statements
Enter fullscreen mode Exit fullscreen mode

Now we've covered only 50% of our coverage as we are yet to write tests for our division function.
For a simple case like this, it's obvious what we need to test but in a larger codebase it's not so apparent and this is where coverage can be very powerful.
Go gives us tools to visualize where exactly in our code have not been tested.
We can do this by passing in some flags to our go test command: go test -coverprofile=c.out. coverprofile generates a coverage profile with the value being the name of the output file, in this case c.out.
Once you have the profile c.out, you can examine it to know more about your code coverage using the go's tool command.
We can check it out on our terminal using the command

go tool cover -func=c.out
Enter fullscreen mode Exit fullscreen mode
github.com/quamejnr/go-cover/main.go:3: Add             100.0%
github.com/quamejnr/go-cover/main.go:7: Div             0.0%
total:                                  (statements)    50.0%
Enter fullscreen mode Exit fullscreen mode

This shows our Div function is yet to have any test coverage so let's add test coverage to our Div function.

func TestDivison(t *testing.T) {
    t.Run("Test division", func(t *testing.T) {
        got := Div(4, 2)
        want := 2
        if got != want {
            t.Errorf("wanted %d got %d", want, got)
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

Now we should have covered everything. Let's run our coverage again and see

go test ./... -cover
Enter fullscreen mode Exit fullscreen mode
ok      github.com/quamejnr/go-cover    0.213s  coverage: 75.0% of statements
Enter fullscreen mode Exit fullscreen mode

It says we have covered only 75% of our test coverage.
And if we generate our cover profile again and inspect it.

go test ./... -coverprofile=c.out
go tool cover -func=c.out
Enter fullscreen mode Exit fullscreen mode
github.com/quamejnr/go-cover/main.go:3: Add             100.0%
github.com/quamejnr/go-cover/main.go:7: Div             66.7%
total:                                  (statements)    75.0%
Enter fullscreen mode Exit fullscreen mode

We can see we have covered only 66.7% of our Div code but this doesn't give us much info on what we're missing out.
This is where another useful visualization tool of the coverprofile comes in.
Go gives us the ability to look at our code in our browser to see which parts are yet to be tested. All we need to do is change our -func flag to -html flag.

go tool cover -html=c.out
Enter fullscreen mode Exit fullscreen mode

Browser visualization of Go test coverage
This screenshot shows us that we're yet to test our path when denom is 0.
Let's update our TestDivison function to handle that.

func TestDivison(t *testing.T) {
    t.Run("Test division", func(t *testing.T) {
        got := Div(4, 2)
        want := 2
        if got != want {
            t.Errorf("wanted %d got %d", want, got)
        }
    })
    t.Run("Test division with 0", func(t *testing.T) {
        defer func() {
          if r := recover(); r == nil {
            t.Errorf("zero divsion did not panic")
          }
        }()
        Div(4, 0)
        })
}
Enter fullscreen mode Exit fullscreen mode

Now if we run our go test coverage command, we should have 100% coverage.

go test ./... -cover
Enter fullscreen mode Exit fullscreen mode
ok      github.com/quamejnr/go-cover    0.649s  coverage: 100.0% of statements
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, we examined how we can use go's coverage tools to help us with our testing coverage
We learnt how to;

  1. create coverage profiles
  2. examine them in our terminal
  3. visualize them in our browsers. All code can be found here https://github.com/quamejnr/go-cover

References

Go Class: 39 Code Coverage by Matt Holiday

Top comments (0)