Packages, modules and tooling · 3 MIN READ

Parallel tests must own their mutable resources

Find races, test caching mistakes and shared integration fixtures before scaling concurrency.

A test can pass alone and fail when other packages or subtests run at the same time. Shared database names, environment variables, ports and mutable globals turn isolated test intent into interference. Give every test its own resources, and distinguish a data race from a wider coordination bug.

Packages and production tooling · A MAP OF THE IDEA

  1. Isolated fixture
  2. Bounded parallelism
  3. Race evidence
  4. Repeat failure

Useful when

  • Find races, test caching mistakes and shared integration fixtures before scaling concurrency.

Watch out

  • Package test binaries and parallel subtests share downstream capacity. Isolate fixtures and bound their combined demand; the race detector does not size a database pool.

Implementation & operation cost: Isolation requires disposable fixtures and cleanup; parallelism still spends shared database and machine capacity.

01

Separate three kinds of parallelism

-p controls how many packages are built or tested concurrently; -parallel controls parallel tests within one test binary; GOMAXPROCS affects Go execution parallelism. Setting all three high can overwhelm a shared database even though each individual test uses only a few connections.

bash
go test -count=1 -shuffle=on ./...
go test -race -count=1 ./...
go test -p=2 -parallel=4 ./...
Read the code

A fresh run bypasses cached test results; shuffle exposes order dependence, and the race detector observes conflicting memory accesses on executed paths. None of these alone validates distributed database invariants.

02

Give temporary resources one owner

Use t.TempDir and t.Cleanup for filesystem state. Pick an OS-assigned listener port rather than a fixed global port. A test that mutates environment or process working directory must respect the testing package restrictions around parallel execution.

go
func TestConfig(t *testing.T) {
    dir := t.TempDir()
    t.Setenv("APP_DATA", dir)
    // This test changes process environment; do not call t.Parallel.
    got := os.Getenv("APP_DATA")
    if got != dir { t.Fatalf("APP_DATA = %q", got) }
}
Read the code

The example is a test-file fragment importing testing and os. The test owns its directory and cleanup, and explicitly stays sequential because environment changes affect the whole process.

03

Test cancellation and resource release

Assert that work exits after cancellation and that database clients, rows, bodies and temporary servers are closed. A passing result with leaked goroutines can poison later tests or exhaust CI resources. Keep a failing shuffled seed or a deterministic reproducer rather than relying on repeated lucky retries.

TAKE THIS WITH YOU

Make test ownership explicit, bound package and subtest parallelism separately, and use race detection as evidence on exercised paths rather than a proof of correctness.

Check your understanding

What prevents tests from overloading one test database?

Go to the source

Progress stays in this browser. No account needed.