Skip to main content
โšก Calmops

Code Coverage and Quality Metrics in Go

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

Summary

Code coverage and quality metrics are essential for Go projects:

  • Use go test -cover to measure coverage
  • Generate HTML reports with go tool cover
  • Use golangci-lint for comprehensive linting
  • Use go vet for 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