如果我有一个带有设置和拆卸逻辑的父测试,我如何并行地运行其中的子测试,而不会遇到带有拆卸逻辑的竞争条件?
func TestFoo(t *testing.T) {
// setup logic
t.Run("a", func(t *testing.T) {
t.Parallel()
// test code
})
// teardown logic
}
举一个人为的例子:假设测试需要创建一个tmp文件,该文件将被所有子测试使用,并在测试结束时将其删除。
t.Parallel()
,因为这是我最终想要的。但是我的问题和下面的输出是相同的,即使父级不调用
t、 平行()
.
如果我按顺序运行子测试,它们不会通过任何问题:
package main
import (
"fmt"
"io/ioutil"
"os"
"testing"
)
func setup(t *testing.T) (tmpFile string) {
f, err := ioutil.TempFile("/tmp", "subtests")
if err != nil {
t.Fatalf("could not setup tmp file: %+v", err)
}
f.Close()
return f.Name()
}
var ncase = 2
func TestSeqSubtest(t *testing.T) {
t.Parallel()
// setup test variables
fname := setup(t)
// cleanup test variables
defer func() {
os.Remove(fname)
}()
for i := 0; i < ncase; i++ {
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
if _, err := os.Stat(fname); os.IsNotExist(err) {
t.Fatalf("file was removed before subtest finished")
}
})
}
}
输出:
$ go test subtests
ok subtests 0.001s
但是,如果我并行运行子测试,那么父测试的拆卸逻辑最终会被调用
之前
"Using Subtests and Sub-benchmarks"
go博客说:
如果测试函数调用
与顺序测试同时运行,其执行是
他回来了。
func TestParallelSubtest(t *testing.T) {
t.Parallel()
// setup test variables
fname := setup(t)
// cleanup test variables
defer func() {
os.Remove(fname)
}()
for i := 0; i < ncase; i++ {
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel() // the change that breaks things
if _, err := os.Stat(fname); os.IsNotExist(err) {
t.Fatalf("file was removed before subtest finished")
}
})
}
}
$ go test subtests
--- FAIL: TestParallelSubtest (0.00s)
--- FAIL: TestParallelSubtest/test_0 (0.00s)
main_test.go:58: file was removed before subtest finished
--- FAIL: TestParallelSubtest/test_1 (0.00s)
main_test.go:58: file was removed before subtest finished
FAIL
FAIL subtests 0.001s
带WaitGroup的并行子测验
如上所述,并行子测试在其父测试完成之前不会执行,这意味着尝试使用
sync.WaitGroup
导致死锁:
func TestWaitGroupParallelSubtest(t *testing.T) {
t.Parallel()
var wg sync.WaitGroup
// setup test variables
fname := setup(t)
// cleanup test variables
defer func() {
os.Remove(fname)
}()
for i := 0; i < ncase; i++ {
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
wg.Add(1)
defer wg.Done()
t.Parallel()
if _, err := os.Stat(fname); os.IsNotExist(err) {
t.Fatalf("file was removed before subtest finished")
}
})
}
wg.Wait() // causes deadlock
}
输出:
$ go test subtests
--- FAIL: TestParallelSubtest (0.00s)
--- FAIL: TestParallelSubtest/test_0 (0.00s)
main_test.go:58: file was removed before subtest finished
--- FAIL: TestParallelSubtest/test_1 (0.00s)
main_test.go:58: file was removed before subtest finished
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
testing.tRunner.func1(0xc00009a000)
/path/to/golang1.1.11/src/testing/testing.go:803 +0x1f3
testing.tRunner(0xc00009a000, 0xc00005fe08)
/path/to/golang1.1.11/src/testing/testing.go:831 +0xc9
testing.runTests(0xc00000a0a0, 0x6211c0, 0x3, 0x3, 0x40b36f)
/path/to/golang1.1.11/src/testing/testing.go:1117 +0x2aa
testing.(*M).Run(0xc000096000, 0x0)
/path/to/golang1.1.11/src/testing/testing.go:1034 +0x165
main.main()
_testmain.go:46 +0x13d
goroutine 7 [semacquire]:
sync.runtime_Semacquire(0xc0000a2008)
/path/to/golang1.1.11/src/runtime/sema.go:56 +0x39
sync.(*WaitGroup).Wait(0xc0000a2000)
/path/to/golang1.1.11/src/sync/waitgroup.go:130 +0x64
subtests.TestWaitGroupParallelSubtest(0xc00009a300)
/path/to/go_code/src/subtests/main_test.go:91 +0x2b5
testing.tRunner(0xc00009a300, 0x540f38)
/path/to/golang1.1.11/src/testing/testing.go:827 +0xbf
created by testing.(*T).Run
/path/to/golang1.1.11/src/testing/testing.go:878 +0x353
goroutine 8 [chan receive]:
testing.runTests.func1.1(0xc00009a000)
/path/to/golang1.1.11/src/testing/testing.go:1124 +0x3b
created by testing.runTests.func1
/path/to/golang1.1.11/src/testing/testing.go:1124 +0xac
goroutine 17 [chan receive]:
testing.(*T).Parallel(0xc0000f6000)
/path/to/golang1.1.11/src/testing/testing.go:732 +0x1fa
subtests.TestWaitGroupParallelSubtest.func2(0xc0000f6000)
/path/to/go_code/src/subtests/main_test.go:85 +0x86
testing.tRunner(0xc0000f6000, 0xc0000d6000)
/path/to/golang1.1.11/src/testing/testing.go:827 +0xbf
created by testing.(*T).Run
/path/to/golang1.1.11/src/testing/testing.go:878 +0x353
goroutine 18 [chan receive]:
testing.(*T).Parallel(0xc0000f6100)
/path/to/golang1.1.11/src/testing/testing.go:732 +0x1fa
subtests.TestWaitGroupParallelSubtest.func2(0xc0000f6100)
/path/to/go_code/src/subtests/main_test.go:85 +0x86
testing.tRunner(0xc0000f6100, 0xc0000d6040)
/path/to/golang1.1.11/src/testing/testing.go:827 +0xbf
created by testing.(*T).Run
/path/to/golang1.1.11/src/testing/testing.go:878 +0x353
FAIL subtests 0.003s
总结
那么,我如何在调用的父测试中拥有一个teardown方法呢
之后
并行子测试运行吗?