Earlier this year I learned that I could make my Go unit test faster, it turned out I wasn’t using the t.Parallel method to its full potential. Say you have the following package.
1
2
3
4
5
6
7
8
9
| package example
func PrintHello() string {
return "Hello, World!"
}
func PrintGoodbye() string {
return "Goodbye, World!"
}
|
With the following tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| package example_test
import (
"example"
"fmt"
"testing"
)
func Test(t *testing.T) {
fmt.Println("Starting tests...")
t.Run("PrintHello", func(t *testing.T) {
fmt.Println("Running PrintHello test")
result := example.PrintHello()
expected := "Hello, World!"
if result != expected {
t.Errorf("Expected %q, got %q", expected, result)
}
})
t.Run("PrintGoodbye", func(t *testing.T) {
fmt.Println("Running PrintGoodbye test")
result := example.PrintGoodbye()
expected := "Goodbye, World!"
if result != expected {
t.Errorf("Expected %q, got %q", expected, result)
}
})
}
|
If I run the go test command on my terminal as follows.
I got this result.
1
2
3
4
5
| Starting tests...
Running PrintHello test
Running PrintGoodbye test
PASS
ok example 0.495s
|
These tests are executed in 0.495 seconds. One way to speed the execution of these test is to to throw a t.Paralle at the top of the test function, like this.
1
2
3
4
5
6
7
8
9
10
11
| func Test(t *testing.T) {
t.Parallel() // enable test parallelism
fmt.Println("Starting tests...")
t.Run("PrintHello", func(t *testing.T) {
// omitted for brevity
})
t.Run("PrintGoodbye", func(t *testing.T) {
// omitted for brevity
})
}
|
Now when I run the go test command I get the following results.
1
2
3
4
5
| Starting tests...
Running PrintHello test
Running PrintGoodbye test
PASS
ok example 0.269s
|
Nice, the tests are faster, the problem with this and what I ended up learning is that we can make even further improvements by making the sub-tests use the t.Parallel function. For some reason I always thought that using t.Parallel at the top level made all the sub-tests run in parallel. Turns out I was mistaken.
1
2
3
4
5
6
7
8
9
10
11
12
13
| func Test(t *testing.T) {
t.Parallel()
fmt.Println("Starting tests...")
t.Run("PrintHello", func(t *testing.T) {
t.Parallel()
// rest of the code omitted for brevity
})
t.Run("PrintGoodbye", func(t *testing.T) {
t.Parallel()
// rest of the code omitted for brevity
})
}
|
With each sub-test now using t.Parallel I get the following result when I execute the go test in my terminal.
1
2
3
4
5
| Starting tests...
Running PrintHello test
Running PrintGoodbye test
PASS
ok example 0.162s
|
Great, the tests are even faster. Now I know, these are small gains, but this is just an example, in a large repo where hundreds or even thousands of test are executed a change like this could improve your test execution time.
Now I have a few notes on t.Parallel.
- Any top level function that does not call t.Parallel will block until all of its sub-test have completed, even if the sub-test does call t.Parallel.
- t.Parallel is a blocking method, it makes the function stop and only resume when all non parallel methods have completed.
- The maximum number of tests to run in parallel is determined by the GOMAXPROCS environment variable.