DEV Community

Cover image for A Complete Introduction to Go Testing
Leapcell
Leapcell

Posted on

A Complete Introduction to Go Testing

Cover

Organization of Test Files

In Go, test files are usually located in the same package as the source files being tested, and follow these naming conventions:

  • Source file: xxx.go
  • Test file: xxx_test.go

Test file names must end with _test.go so that the Go toolchain can recognize and treat them as test files.

Signature of Test Functions

Test functions must have the following signature:

func TestXxx(t *testing.T) { ... }
Enter fullscreen mode Exit fullscreen mode
  • The function name starts with Test, followed by the name of the function or feature being tested (usually with an uppercase initial).
  • It takes a single parameter *testing.T, which is used to report test failures and log information.

Writing Test Functions

Basic Example

Suppose there is a simple addition function Add, located in the math.go file:

// math.go
package mathutil

func Add(a, b int) int {
    return a + b
}
Enter fullscreen mode Exit fullscreen mode

The corresponding test file math_test.go can be written as follows:

// math_test.go
package mathutil

import (
    "testing"
)

func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }

    for _, tt := range tests {
        result := Add(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Add(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Using Subtests

Go 1.7 introduced subtests, making it more convenient to run multiple related tests within the same test function:

func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }

    for _, tt := range tests {
        t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Using Table-Driven Tests

Table-driven testing is a common pattern where a set of inputs and expected outputs are defined to test a function’s behavior:

func TestMultiply(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {2, 3, 6},
        {0, 5, 0},
        {-2, 4, -8},
    }

    for _, tt := range tests {
        t.Run(fmt.Sprintf("%d*%d", tt.a, tt.b), func(t *testing.T) {
            result := Multiply(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Multiply(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Running Tests

Using go test Command

In the package directory containing the test files, run the following command to execute all tests:

go test
Enter fullscreen mode Exit fullscreen mode

Running Specific Tests

To run a specific test function, you can use the -run parameter, which supports regular expressions:

go test -run TestAdd
Enter fullscreen mode Exit fullscreen mode

Viewing Detailed Output

Use the -v flag to see detailed output for each test function:

go test -v
Enter fullscreen mode Exit fullscreen mode

Running Tests in Parallel

Use t.Parallel() to run test functions concurrently, improving testing efficiency:

func TestSomething(t *testing.T) {
    t.Parallel()
    // test code
}
Enter fullscreen mode Exit fullscreen mode

When running tests, you can specify the number of tests to execute in parallel with -parallel:

go test -parallel 4
Enter fullscreen mode Exit fullscreen mode

Test Coverage

Use go test -cover to check test coverage.

Generate a coverage file:

go test -coverprofile=coverage.out
go tool cover -html=coverage.out
Enter fullscreen mode Exit fullscreen mode

Use go test -cover to check test coverage.

Generate a coverage file:

go test -coverprofile=coverage.out
go tool cover -html=coverage.out
Enter fullscreen mode Exit fullscreen mode

Example: Complete Testing Workflow

Here is a comprehensive example showing how to write, run, and check test coverage.

Source Code

// mathutil/math.go
package mathutil

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

func Multiply(a, b int) int {
    return a * b
}
Enter fullscreen mode Exit fullscreen mode

Test Code

// mathutil/math_test.go
package mathutil

import (
    "testing"
)

func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }

    for _, tt := range tests {
        t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Running Tests and Coverage

go test -v -coverprofile=coverage.out
go tool cover -html=coverage.out
Enter fullscreen mode Exit fullscreen mode

Go provides powerful and concise testing tools, making it very convenient to write and maintain tests. By organizing test files properly, writing testable code, and regularly checking test coverage, developers can ensure code quality and reliability. Combined with continuous integration, automated testing becomes an important means of ensuring project stability.


We are Leapcell, your top choice for hosting Go projects.

Leapcell

Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:

Multi-Language Support

  • Develop with Node.js, Python, Go, or Rust.

Deploy unlimited projects for free

  • pay only for usage — no requests, no charges.

Unbeatable Cost Efficiency

  • Pay-as-you-go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real-time metrics and logging for actionable insights.

Effortless Scalability and High Performance

  • Auto-scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Explore more in the Documentation!

Try Leapcell

Follow us on X: @LeapcellHQ


Read on our blog

Top comments (0)