This post documents some testing I did around whether unit tests in Golang are run in parallel.

TLDR: Tests in a given package run serially, unless t.Parallel() is specified.

To test this, I created two files in a directory named gotest:

$ ls
one_test.go  two_test.go
// one_test.go
package main

import (
    "fmt"
    "testing"
    "time"
)

func TestOne(t *testing.T) {
    fmt.Println("1 starts:", time.Now().String())
    time.Sleep(5 * time.Second)
    fmt.Println("1 ends:", time.Now().String())
}
// two_test.go
package main

import (
    "fmt"
    "testing"
    "time"
)

func TestTwo(t *testing.T) {
    fmt.Println("2 starts:", time.Now().String())
    fmt.Println("2 ends:", time.Now().String())
}

When I ran the tests, you can see that TestOne blocked TestTwo from running for five seconds:

$ go test -v -count 1 one_test.go two_test.go
=== RUN   TestOne
1 starts: 2024-10-18 07:56:48.504461 -0700 PDT m=+0.001203001
1 ends: 2024-10-18 07:56:53.505833 -0700 PDT m=+5.002603251
--- PASS: TestOne (5.00s)
=== RUN   TestTwo
2 starts: 2024-10-18 07:56:53.506276 -0700 PDT m=+5.003046710 <---- Does not start until TestOne finishes
2 ends: 2024-10-18 07:56:53.506289 -0700 PDT m=+5.003059210
--- PASS: TestTwo (0.00s)
PASS
ok      command-line-arguments  5.400s
[07:56] widget ~/.../go/gotest $

Interestingly, it appears that the order in which I specify test files matters. If I reverse them, TwoTest runs first and is not blocked:

$ go test -v -count 1 two_test.go one_test.go
=== RUN   TestTwo  <-------------- Starts first because the file order changed!
2 starts: 2024-10-18 07:58:01.635143 -0700 PDT m=+0.000389543
2 ends: 2024-10-18 07:58:01.635226 -0700 PDT m=+0.000473001
--- PASS: TestTwo (0.00s)
=== RUN   TestOne
1 starts: 2024-10-18 07:58:01.635245 -0700 PDT m=+0.000491960
1 ends: 2024-10-18 07:58:06.636325 -0700 PDT m=+5.001599543
--- PASS: TestOne (5.00s)
PASS
ok      command-line-arguments  5.204s

If I modify the tests to add t.Parallal()

// one_test.go
package main

import (
    "fmt"
    "testing"
    "time"
)

func TestOne(t *testing.T) {
    t.Parallel() // this is new

    fmt.Println("1 starts:", time.Now().String())
    time.Sleep(5 * time.Second)
    fmt.Println("1 ends:", time.Now().String())
}
// two_test.go
package main

import (
    "fmt"
    "testing"
    "time"
)

func TestTwo(t *testing.T) {
    t.Parallel() // this is new

    fmt.Println("2 starts:", time.Now().String())
    fmt.Println("2 ends:", time.Now().String())
}

…then the tests run in parallel, which makes sense:

[07:59] widget ~/.../go/gotest $ go test -v -count 1 one_test.go two_test.go
=== RUN   TestOne
=== PAUSE TestOne
=== RUN   TestTwo
=== PAUSE TestTwo
=== CONT  TestOne
=== CONT  TestTwo
1 starts: 2024-10-18 07:59:38.016855 -0700 PDT m=+0.001054334
2 starts: 2024-10-18 07:59:38.016932 -0700 PDT m=+0.001131209
2 ends: 2024-10-18 07:59:38.017105 -0700 PDT m=+0.001304543
--- PASS: TestTwo (0.00s)
1 ends: 2024-10-18 07:59:43.018119 -0700 PDT m=+5.002346709
--- PASS: TestOne (5.00s)
PASS
ok      command-line-arguments  5.209s
[07:59] widget ~/.../go/gotest $

This blog post was helpful when researching this.