Chapter 26: Parallel Tests¶
Description¶
Test concurrent access to shared data with t.Parallel(), sync.Mutex, and sync/atomic. Unsafe operations (plain int increment, slice append without lock) produce data races; safe variants using mutexes or atomics prevent them. Run tests with go test -race to detect unsynchronized access.
Code¶
type UnsafeCounter struct { value int }
func (c *UnsafeCounter) Increment() { c.value++ }
type SafeCounter struct {
mu sync.Mutex
value int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
type AtomicCounter struct {
value atomic.Int64
}
func (c *AtomicCounter) Increment() { c.value.Add(1) }
Test¶
func TestUnsafeCounter_ParallelRace(t *testing.T) {
c := &UnsafeCounter{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Increment()
}()
}
wg.Wait()
// Run with -race: detects data race on c.value
t.Logf("final value: %d (expected 1000)", c.Value())
}
func TestSafeCounter_Parallel(t *testing.T) {
c := &SafeCounter{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Increment()
}()
}
wg.Wait()
assert.Equal(t, 1000, c.Value())
}
func TestAtomicCounter_Parallel(t *testing.T) {
c := &AtomicCounter{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Increment()
}()
}
wg.Wait()
assert.Equal(t, int64(1000), c.Value())
}
func TestParallelSubtests(t *testing.T) {
values := []int{1, 2, 3, 4, 5}
for _, v := range values {
v := v
t.Run("", func(t *testing.T) {
t.Parallel()
assert.Greater(t, v, 0)
})
}
}
Testing Approach¶
Parallel test patterns:
- Race detection —
TestUnsafeCounter_ParallelRacedemonstrates a data race. Running withgo test -racereports the race. TheSafeCounterandAtomicCountertests are race-free. wg.Wait()for goroutine completion — all parallel tests usesync.WaitGroupto ensure goroutines finish before assertions. Without it, some goroutines may not have incremented yet.t.Parallel()for subtests —TestParallelSubtestsruns each subtest in parallel witht.Parallel(). Thev := vcopy captures the loop variable per-iteration, preventing the closure-iteration bug.sync/atomicfor single-value contention —AtomicCounterusesatomic.Int64.Add(1)which is lock-free and faster than a mutex for simple counters. Usesync.Mutexwhen protecting multi-field structs or compound operations.
View source code on GitHub