Testing
1. Basic Testing in Go
Go has a built-in testing framework in the testing
package.
- Test needs to be in a file
*_test.go
. - Test functions need to be prefixed with
Test
.
Basic Test Example:
package main
import "testing"
// Function to be tested
func Add(a, b int) int {
return a + b
}
// --- code in `add_test.go` file
// Test function
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Expected %d but got %d", expected, result)
}
}
Test
followed by the function being tested.
- t *testing.T
is passed to the test function to report errors.
- t.Errorf
is used to report a test failure.
Run the tests:
2. Table-Driven Tests
Table-driven tests are a Go idiom for testing different inputs and expected results in a concise and easy-to-maintain format.
func TestAdd(t *testing.T) {
tests := []struct {
a, b int
expected int
}{
{2, 3, 5},
{5, 7, 12},
{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("Expected %d but got %d", tt.expected, result)
}
})
}
}
3. Testing with Setup and Teardown
You might need setup or teardown functions for initializing and cleaning up resources.
Example with testing.T
Setup:
package main
import (
"fmt"
"testing"
)
func setup() {
fmt.Println("Setting up")
}
func teardown() {
fmt.Println("Tearing down")
}
func TestAdd(t *testing.T) {
setup()
defer teardown() // Ensures teardown happens after test completes
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5 but got %d", result)
}
}
4. Mocking in Go
Go doesnβt have built-in mocking tools, but you can easily create mock implementations or use third-party libraries like github.com/stretchr/testify/mock
for more complex mocking.
Manual Mocking
If you're testing a function that depends on an external interface or a database, you can create a manual mock.
Example with Interface:
package main
import "testing"
// Define an interface
type Database interface {
Save(data string) error
}
// Implement a mock struct
type MockDatabase struct{}
func (m *MockDatabase) Save(data string) error {
if data == "" {
return fmt.Errorf("empty data")
}
return nil
}
func TestSaveData(t *testing.T) {
mockDB := &MockDatabase{}
err := mockDB.Save("data")
if err != nil {
t.Errorf("Expected no error but got %v", err)
}
err = mockDB.Save("")
if err == nil {
t.Error("Expected error for empty data")
}
}
Mocking with testify/mock
testify
is a popular library for mocking and assertions. Install it via:
Mock Example with testify/mock
:
package main
import (
"testing"
"github.com/stretchr/testify/mock"
)
// Define an interface
type Database interface {
Save(data string) error
}
// Define a mock struct using testify
type MockDatabase struct {
mock.Mock
}
func (m *MockDatabase) Save(data string) error {
args := m.Called(data)
return args.Error(0)
}
// Test function using the mock
func TestSaveDataWithMock(t *testing.T) {
mockDB := new(MockDatabase)
mockDB.On("Save", "data").Return(nil) // Mock behavior
mockDB.On("Save", "").Return(fmt.Errorf("empty data"))
err := mockDB.Save("data")
if err != nil {
t.Errorf("Expected no error but got %v", err)
}
err = mockDB.Save("")
if err == nil {
t.Error("Expected error for empty data")
}
mockDB.AssertExpectations(t) // Assert all expected calls were made
}
testify/mock
allows you to:
- Set expectations (mockDB.On()
).
- Verify that expectations were met (mockDB.AssertExpectations()
).
5. Benchmarking
Go also supports benchmarking with the testing
package using the Benchmark
function.
Example:
To run the benchmark:
Benchmarking Specific Functions: You can benchmark individual functions like this:
6. Code Coverage
Go supports built-in code coverage.
Run Tests with Coverage:
Show Detailed Coverage:
This will generate a nice HTML file showing the code coverage details.
7. Parallel Tests
To run tests in parallel (useful for independent tests), you can use t.Parallel()
.
func TestAdd1(t *testing.T) {
t.Parallel() // Run this test in parallel with others
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5 but got %d", result)
}
}
func TestAdd2(t *testing.T) {
t.Parallel()
result := Add(5, 7)
if result != 12 {
t.Errorf("Expected 12 but got %d", result)
}
}
8. Test Suites (Multiple Tests for the Same Group)
Test suites arenβt built-in, but you can group tests logically by creating a helper function for repeated setup or assertions.
Test Suite Example:
package main
import "testing"
func setupSuite() {
// Setup code for the suite
}
func teardownSuite() {
// Cleanup code for the suite
}
func TestSuite(t *testing.T) {
setupSuite()
defer teardownSuite()
t.Run("TestCase1", TestAdd1)
t.Run("TestCase2", TestAdd2)
}
9. Additional Testing Tools
- Test coverage tools: Like
go-coverage
for more advanced code coverage analysis. - GoMock: Another popular mocking library (install via
go get github.com/golang/mock/gomock
). - Testify Assertions: Itβs useful for easy assertions:
10. Summary of Testing Workflow
- Write a function to be tested.
- Write a test function that checks the behavior.
- Use assertions to check that the result matches expectations.
- Mock external dependencies if needed.
- Use
t.Parallel()
for concurrent testing. - Benchmark critical parts of your code to ensure performance.
- Run tests using
go test
.