Mastering Go Testing
Ethan Miller
Product Engineer · Leapcell

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) { ... }
- 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 }
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) } } }
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) } }) } }
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) } }) } }
Running Tests
Using go test
Command
In the package directory containing the test files, run the following command to execute all tests:
go test
Running Specific Tests
To run a specific test function, you can use the -run
parameter, which supports regular expressions:
go test -run TestAdd
Viewing Detailed Output
Use the -v
flag to see detailed output for each test function:
go test -v
Running Tests in Parallel
Use t.Parallel()
to run test functions concurrently, improving testing efficiency:
func TestSomething(t *testing.T) { t.Parallel() // test code }
When running tests, you can specify the number of tests to execute in parallel with -parallel
:
go test -parallel 4
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
Use go test -cover
to check test coverage.
Generate a coverage file:
go test -coverprofile=coverage.out go tool cover -html=coverage.out
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 }
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) } }) } }
Running Tests and Coverage
go test -v -coverprofile=coverage.out go tool cover -html=coverage.out
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 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!
Follow us on X: @LeapcellHQ