DEV Community

Alexander Ertli
Alexander Ertli

Posted on

Scaling Go Tests with Smart Naming Conventions

(How I accidentally built a test naming system that worked)

It's common mantra in the Go ecosystem to test with the real thing whenever possible. But as our codebase grew, running tests with go test -v became a reason to take a 5-minute coffee break. Here's how we solved it.

โšก๏ธ Quick Go Testing Refresher

Go's testing conventions are beautifully simple:

  • Test files: End with _test.go (e.g., service.go โ†’ service_test.go)
  • Test functions: Start with TestXxx(t *testing.T)
  • Run tests:
  go test ./...          # All tests in subdirectories  
  go test -v             # Verbose output  
  go test -run TestFoo   # Run specific tests  
Enter fullscreen mode Exit fullscreen mode

Now let's scale this foundation.

๐Ÿ” Understanding Test Types in Go

As our system grew, we needed clear test categories:

  1. Unit Tests

    • Test isolated logic (pure functions, single components)
    • Example: ValidateUserEmail() formatting
    • Scope: Single package, no cross-boundary calls
    • Speed: Instant (no I/O, only Go code)
  2. Integration Tests

    • Test interactions with real dependencies (DBs, APIs, Testcontainers)
    • and cross-package boundaries
    • Example: UserService.Save() writing to PostgreSQL + calling auditlog package
    • Speed: Slower (requires live services)
  3. End-to-End (E2E) Tests

    • Test full system workflows
    • Example: HTTP API calls from auth to DB persistence or testing SDK-interfaces
    • Speed: Very slow

With these categories defined, we needed a way to run them separately.

๐Ÿ”ง First Attempt: Environment Variables

if os.Getenv("INTEGRATION_TESTS") == "" {
    t.Skip("Skipping integration test")
}
Enter fullscreen mode Exit fullscreen mode

Problems emerged:

  • IDE test runners would "pass" (but actually skip) tests
  • CI configurations became bloated
  • Easy to forget setting env vars

๐Ÿงช Second Try: Regex in Test Names

Next try postfixing:

func TestUserService_Integration(t *testing.T) 
Enter fullscreen mode Exit fullscreen mode

And running:

go test -run '/Integration$'
Enter fullscreen mode Exit fullscreen mode

But this broke constantly in the monorepo with -C directory changes.

๐Ÿ’ก The Solution: Go-Native Prefix Naming

Standardized on:

  • TestUnit_ for fast, isolated tests
  • TestIntegration_ for cross-package/dependency tests
func TestUnit_User_ValidateEmail(t *testing.T) {}
func TestIntegration_UserService_CreateWithAuditLog(t *testing.T) {}
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ Why This Works

  1. Blazing Fast Local Runs
   go test -run ^TestUnit_ ./...  # 0.3s vs 3 minutes
Enter fullscreen mode Exit fullscreen mode
  1. Clear Intent
   TestUnit_Parser_HandlesEdgeCases
   TestIntegration_PaymentService_ProcessesRefund
Enter fullscreen mode Exit fullscreen mode
  1. Simplified CI
   jobs:
     unit:
       run: go test -run ^TestUnit_ ./...
     integration:
       run: go test -run ^TestIntegration_ ./...
Enter fullscreen mode Exit fullscreen mode
  1. IDE Friendly Every Go-aware editor understands these prefix conventions.

๐Ÿ“ฆ Key Takeaways

  1. Names Are Contracts - TestUnit_/TestIntegration_ creates clear boundaries
  2. Fail Fast - Slow tests shouldn't sneak into quick runs
  3. Start Small - Just rename one test today

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.