“I pushed to CI and went to make coffee… then forgot what I was working on”
go test -json ./... | go-test-trace
go test -v ./... 2>&1 | grep -E '(PASS|FAIL).*\('
go test ./... -json | \
jq -r 'select(.Action == "pass") |
[.Elapsed, .Package] | @tsv' | \
sort -rn | head -20
go install github.com/lamarios/vgt@latest
vgt
Two main levers:
t.Parallel() - parallel tests within package-p flag - parallel package executionfunc TestUserCreation(t *testing.T) {
t.Parallel()
// Test runs concurrently with other parallel tests
}
# Run 4 packages concurrently
go test -p 4 ./...
# Check what -p would do without running
go test -p 4 -n ./...
go test -p 4 -parallel 8 ./...
-p 4: 4 packages at once-parallel 8: 8 tests per packagePostgreSQL feature: clone databases instantly
CREATE DATABASE testdb
TEMPLATE template1;
Traditional approach:
Test 1: 100ms migrations + 50ms test
Test 2: 100ms migrations + 50ms test
Total: 300ms
Template approach:
Setup: 100ms migrations (once)
Test 1: 10ms clone + 50ms test
Test 2: 10ms clone + 50ms test
Total: 220ms
Setting up template databases in Go
var initDatabaseOnce sync.Once
func setupTemplate(t *testing.T) {
initDatabaseOnce.Do(func() {
db := connectToPostgres()
defer db.Close()
// Run migrations on template1
runMigrations(db, "template1")
})
}
func createTestDB(t *testing.T) *sql.DB {
setupTemplate(t)
dbName := fmt.Sprintf("test_%s_%d",
t.Name(),
time.Now().UnixNano(),
)
// Terminate existing connections
terminateConnections(dbName)
// Create from template
db := connectToPostgres()
db.Exec(fmt.Sprintf(
"CREATE DATABASE %s TEMPLATE template1",
dbName,
))
return connectToTestDB(dbName)
}
func TestUser(t *testing.T) {
t.Parallel()
db := createTestDB(t)
t.Cleanup(func() {
dbName := db.Stats().Name
db.Close()
admin := connectToPostgres()
defer admin.Close()
terminateConnections(dbName)
admin.Exec(fmt.Sprintf(
"DROP DATABASE %s",
dbName,
))
})
// Your test here
}
Beyond template databases
services:
postgres:
image: postgres:16
command: postgres -c fsync=off -c full_page_writes=off
services:
postgres:
image: postgres:16
tmpfs:
- /var/lib/postgresql/data
services:
postgres:
image: postgres:16
command: postgres -c fsync=off
tmpfs:
- /var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: template1
Hard-learned lessons
// Bad: connection not closed
db := createTestDB(t)
// test code...
// Good: ensure cleanup
db := createTestDB(t)
t.Cleanup(func() {
db.Close()
})
// Bad: modifying template
db := connectTo("template1")
db.Exec("INSERT INTO users...")
// Good: only read from template
db := connectTo("template1")
// Read-only operations or create test DB
// Bad: predictable name
dbName := "test_db"
// Good: unique name
dbName := fmt.Sprintf("test_%s_%d",
sanitize(t.Name()),
time.Now().UnixNano(),
)
postgres:
environment:
POSTGRES_MAX_CONNECTIONS: 200
// In tests
db.SetMaxOpenConns(5)
db.SetMaxIdleConns(2)
From the articles
# Before
go test ./...
# 45 seconds
# After: templates + parallel
go test -p 4 ./...
# 6 seconds
Three key techniques:
t.Parallel() + -p)t.Parallel()-p flagExample repo: https://gitlab.com/hmajid2301/banterbus
Slides: https://haseebmajid.dev/slides/faster-go-tests-and-postgres/