Code Coverage and Quality Metrics in Go
Code coverage and quality metrics are essential for maintaining high-quality Go applications. Go provides built-in tools for measuring coverage, and various third-party tools help analyze code quality. This guide covers both.
Measuring Code Coverage
Basic Coverage Analysis
# Run tests with coverage
go test -cover
# Generate coverage profile
go test -coverprofile=coverage.out
# View coverage report
go tool cover -html=coverage.out
Coverage in Code
package main
import (
"testing"
)
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d; want 5", result)
}
}
// Subtract is not tested - will show in coverage report
Coverage Modes
# Set coverage mode
go test -covermode=set -coverprofile=coverage.out
go test -covermode=count -coverprofile=coverage.out
go test -covermode=atomic -coverprofile=coverage.out
Analyzing Coverage Output
package main
import (
"fmt"
"log"
"os"
"os/exec"
)
func main() {
// Run tests with coverage
cmd := exec.Command("go", "test", "-coverprofile=coverage.out", "./...")
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
// Parse coverage output
cmd = exec.Command("go", "tool", "cover", "-func=coverage.out")
output, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
fmt.Println(string(output))
}
Code Quality Tools
Using golangci-lint
# Install golangci-lint
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Run linter
golangci-lint run
# Run specific linters
golangci-lint run --enable=gofmt,govet
Configuration File (.golangci.yml)
linters:
enable:
- gofmt
- govet
- golint
- errcheck
- staticcheck
- unused
issues:
exclude-rules:
- path: _test\.go
linters:
- gocyclo
Using go vet
# Run go vet
go vet ./...
# Check specific issues
go vet -composites=false ./...
Practical Examples
Coverage Report Generator
package main
import (
"bufio"
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
)
type CoverageStats struct {
Total float64
Covered float64
Percent float64
}
func parseCoverage(filename string) (*CoverageStats, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
stats := &CoverageStats{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "total:") {
parts := strings.Fields(line)
if len(parts) >= 2 {
percent := strings.TrimSuffix(parts[len(parts)-1], "%")
if p, err := strconv.ParseFloat(percent, 64); err == nil {
stats.Percent = p
}
}
}
}
return stats, nil
}
func main() {
// Run tests with coverage
cmd := exec.Command("go", "test", "-coverprofile=coverage.out", "./...")
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
// Parse coverage
stats, err := parseCoverage("coverage.out")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Coverage: %.2f%%\n", stats.Percent)
// Generate HTML report
cmd = exec.Command("go", "tool", "cover", "-html=coverage.out", "-o=coverage.html")
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
fmt.Println("Coverage report generated: coverage.html")
}
Quality Metrics Dashboard
package main
import (
"fmt"
"os/exec"
"strings"
)
type QualityMetrics struct {
Coverage float64
LintErrors int
VetErrors int
TestsPassed int
TestsFailed int
}
func getMetrics() (*QualityMetrics, error) {
metrics := &QualityMetrics{}
// Get coverage
cmd := exec.Command("go", "test", "-cover", "./...")
output, _ := cmd.Output()
// Parse coverage from output
// Get lint errors
cmd = exec.Command("golangci-lint", "run", "--out-format=json")
output, _ = cmd.Output()
// Parse lint errors from JSON
// Get vet errors
cmd = exec.Command("go", "vet", "./...")
output, _ = cmd.Output()
// Count vet errors
return metrics, nil
}
func printMetrics(m *QualityMetrics) {
fmt.Println("=== Code Quality Metrics ===")
fmt.Printf("Coverage: %.2f%%\n", m.Coverage)
fmt.Printf("Lint Errors: %d\n", m.LintErrors)
fmt.Printf("Vet Errors: %d\n", m.VetErrors)
fmt.Printf("Tests Passed: %d\n", m.TestsPassed)
fmt.Printf("Tests Failed: %d\n", m.TestsFailed)
}
func main() {
metrics, err := getMetrics()
if err != nil {
fmt.Println("Error:", err)
return
}
printMetrics(metrics)
}
Continuous Coverage Tracking
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"time"
)
type CoverageRecord struct {
Date time.Time
Percent float64
Commit string
}
func recordCoverage(filename string) error {
// Run tests with coverage
cmd := exec.Command("go", "test", "-coverprofile=coverage.out", "./...")
if err := cmd.Run(); err != nil {
return err
}
// Get current commit
cmd = exec.Command("git", "rev-parse", "HEAD")
commitOutput, _ := cmd.Output()
commit := string(commitOutput)
// Create record
record := CoverageRecord{
Date: time.Now(),
Percent: 85.5, // Parse from coverage.out
Commit: commit,
}
// Append to history
file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
data, _ := json.Marshal(record)
file.Write(append(data, '\n'))
return nil
}
func main() {
if err := recordCoverage("coverage_history.json"); err != nil {
log.Fatal(err)
}
fmt.Println("Coverage recorded")
}
Best Practices
โ Good Practices
// Aim for high coverage
go test -cover ./...
// Use coverage profiles
go test -coverprofile=coverage.out
// Run linters regularly
golangci-lint run
// Check for common issues
go vet ./...
// Track coverage over time
// Maintain coverage history
// Set coverage thresholds
// Fail CI if coverage drops
โ Anti-Patterns
// Don't ignore coverage
// Monitor it regularly
// Don't chase 100% coverage
// Focus on meaningful tests
// Don't ignore linter warnings
// Fix issues promptly
// Don't skip quality checks
// Make them part of CI/CD
Resources
- Go Coverage Tool Documentation
- golangci-lint Documentation
- Go vet Documentation
- Code Coverage Best Practices
Summary
Code coverage and quality metrics are essential for Go projects:
- Use
go test -coverto measure coverage - Generate HTML reports with
go tool cover - Use golangci-lint for comprehensive linting
- Use
go vetfor common issues - Track coverage over time
- Set coverage thresholds in CI/CD
- Focus on meaningful tests, not just coverage percentage
With these tools and practices, you can maintain high-quality Go applications.
Comments