Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Tools
Online Compiler JSON Formatter Regex Tester CSS Unit Converter Color Picker
Compiler Tools

Golang Testing: Unit Tests, Table Tests, Benchmarks and Coverage

Unit Tests

Golang includes a standard testing package, so you do not need a third-party framework to start writing useful tests. Test files end with _test.go, and test functions begin with Test.

A test receives *testing.T. Use t.Fatalf when the test cannot continue and t.Errorf when you want to report a failure and continue checking. Clear failure messages make tests easier to debug later.

Testing HelperPurpose
t.Fatal / t.FatalfFail the test and stop immediately.
t.Error / t.ErrorfRecord a failure and continue.
t.Helper()Mark a helper so failure lines point to the caller.
t.RunRun a named subtest.
Simple Test
package calculator

import "testing"

func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    got := Add(2, 3)
    want := 5

    if got != want {
        t.Fatalf("got %d, want %d", got, want)
    }
}

Running Tests

Run tests with go test. In a module, go test ./... runs tests in the current package and all nested packages.

Keep tests fast enough to run often. Fast tests become part of normal development instead of a separate ceremony that developers avoid.

Common Test Commands
go test
go test ./...
go test -v ./...
go test ./... -run TestAdd

Table-Driven Tests

Table-driven tests are a common Golang pattern. Put cases in a slice, loop over them, and run the same assertion logic for each case.

Use t.Run to name each case. Named subtests make failures easier to understand and allow you to run one case by name. They also keep edge cases close to the behavior they verify.

Table Test
func TestNormalize(t *testing.T) {
    tests := []struct {
        name  string
        input string
        want  string
    }{
        {"lowercase", "Go", "go"},
        {"spaces", "Go Tutorial", "go-tutorial"},
        {"trim spaces", "  API Guide  ", "api-guide"},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Normalize(tt.input)
            if got != tt.want {
                t.Fatalf("got %q, want %q", got, tt.want)
            }
        })
    }
}

Test Helpers

When setup or assertions repeat across tests, move them into helper functions. Call t.Helper() inside the helper so failure line numbers point to the test that called it.

Helpers should make tests easier to read, not hide the behavior being tested. Keep important inputs and expected outputs visible in the test itself.

Assertion Helper
func requireEqual[T comparable](t *testing.T, got, want T) {
    t.Helper()

    if got != want {
        t.Fatalf("got %v, want %v", got, want)
    }
}

func TestAddWithHelper(t *testing.T) {
    requireEqual(t, Add(2, 3), 5)
}

Coverage, Benchmarks, and Race Checks

Coverage shows which statements tests execute. Use it as a signal, not as the only quality goal. Good tests verify meaningful behavior, edge cases, and failure paths.

Benchmarks begin with Benchmark and run a loop based on b.N. For concurrent code, go test -race helps detect unsafe shared memory access.

CommandPurpose
go test ./...Run all tests
go test -cover ./...Show coverage
go test -bench=. ./...Run benchmarks
go test -race ./...Detect data races
go test -coverprofile=coverage.out ./...Write a coverage profile

Testing HTTP Handlers

The standard library includes net/http/httptest, which lets you test handlers without opening a real network port. This keeps API tests fast and deterministic.

Test status codes, response bodies, invalid methods, malformed JSON, missing fields, and service errors. Handler tests should prove the HTTP boundary behaves correctly, while service tests can focus on business logic.

Handler Test
func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    rec := httptest.NewRecorder()

    HealthHandler(rec, req)

    if rec.Code != http.StatusOK {
        t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK)
    }

    if strings.TrimSpace(rec.Body.String()) != "ok" {
        t.Fatalf("unexpected body: %q", rec.Body.String())
    }
}

What to Test

Prioritize behavior over implementation details. A good test should explain what the code promises to do, not merely repeat how the code is written.

Useful tests cover normal cases, edge cases, invalid input, error paths, and important concurrency behavior. Avoid testing private helpers directly when a public behavior test would be clearer.

Key Takeaways
  • Test files end with _test.go and test functions begin with Test.
  • Table-driven tests keep related cases compact and easy to extend.
  • Use helper functions for repeated assertions, and call t.Helper() inside them.
  • Use coverage as a signal, not the only goal.
  • Use the race detector for concurrent code.
  • Use httptest for fast HTTP handler tests.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.