gpt4 book ai didi

go - 并行表驱动的go测试失败

转载 作者:行者123 更新时间:2023-12-01 22:07:03 26 4
gpt4 key购买 nike

我有以下测试功能


func TestIntegrationAppsWithProductionSelf(t *testing.T) {
// here is where the apps array that will act as my test suite is being populated
myapps, err := RetrieveApps(fs)
for _, v := range apps {
v := v
t.Run("", func(t *testing.T) {
t.Parallel()
expectedOutput = `=` + v + `
`
cmpOpts.SingleApp = v
t.Logf("\t\tTesting %s\n", v)
buf, err := VarsCmp(output, cmpOpts)
if err != nil {
t.Fatalf("ERROR executing var comparison for %s: %s\n", v, err)
}
assert.Equal(t, expectedOutput, buf.String())
})
}
}

尽管我删除了 t.Parallel()(甚至保留了子测试结构),测试仍然成功,但是测试失败了。

失败(仅当合并了 t.Parallel()时才发生这种情况)与以下事实有关:传递给assert的要比较的值不同步,即 assert方法比较了不应该比较的值)

这是为什么?
我还对测试套件变量( v := v)进行了这种神秘的重新分配,我不理解)

编辑:徘徊是否是 this包中 assert方法的用法,我做了以下替换,尽管最终结果是相同的,

    //assert.Equal(t, expectedOutput, buf.String())
if expectedOutput != buf.String() {
t.Errorf("Failed! Expected %s - Actual: %s\n", expectedOutput, buf.String())
}

最佳答案

让我们分析一下情况。

首先,让我们引用the docs on testing.T.Run :

Run runs f as a subtest of t called name. It runs f in a separate goroutine <…>



(强调我的。)

因此,当您调用 t.Run("some_name", someFn)时,测试套件正在运行该 SomeFn,就像您手动执行类似操作

go someFn(t)

接下来,请注意,您没有将命名函数传递给对 t.Run的调用,而是将其传递给所谓的函数文字。让我们引用 the language spec on them:

Function literals are closures: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.



在您的情况下,这意味着编译器在编译函数文字的主体时,会使函数“覆盖”其主体所提及的任何变量,而这不是形式函数的参数之一;在您的情况下,唯一的函数参数是 t *testing.T,因此创建的闭包会捕获所有其他访问的变量。

在Go中,当函数文字关闭变量时,它会通过保留对该变量的引用来实现这一点-在规范中明确提及为(«然后,这些变量在周围的函数与函数文字<...之间共享 … >»,再次强调我的意思。)

现在,请注意Go中的循环在每次迭代时都重复使用迭代变量;也就是说,当你写

for _, v := range apps {

该变量 v在循环的“外部”范围内创建一次,然后在循环的每次迭代中重新分配。回顾一下:同一变量的存储位于内存中的某个固定点,因此每次迭代都会为其分配一个新值。

现在,由于函数文字通过保留对外部变量的引用来封闭外部变量(而不是将其定义的“时间”将其值复制到自身中),因此无需那种看上去很时髦的 v := v“欺骗”每次调用时创建的每个函数文字循环中的 t.Run将引用与循环完全相同的迭代变量 vv := v构造函数声明了另一个名为 v的变量,该变量在循环主体中是本地的,同时为它分配了循环迭代变量 v的值。由于本地 v“shadows”循环迭代器的 v,此后声明的函数文字将覆盖该局部变量,因此,在每次迭代中创建的每个函数文字都将覆盖不同的,独立的变量 v

您可能会问为什么需要这样做?

之所以需要这样做是因为循环迭代变量和goroutines的相互作用存在一个微妙的问题,即 detailed on the Go wiki:
当某人做某事时

for _, v := range apps {
go func() {
// use v
}()
}

创建关闭 v的函数文字,然后使用 go语句运行-与运行循环的goroutine以及在 len(apps)-1其他迭代上启动的所有其他goroutine并行。
这些运行函数文字的goroutine都引用相同的 v,因此它们都在该变量上进行数据竞争:运行looop的goroutine对其进行写入,并且运行函数文字的goroutines从中读取数据-同时并没有任何同步。

我希望,到目前为止,您应该可以看到难题的各个部分:在代码中

    for _, v := range apps {
v := v
t.Run("", func(t *testing.T) {
expectedOutput = `=` + v + `
// ...

传递给 t.Run的函数文字会关闭 vexpectedOutputcmpOpts.SingleApp(可能还有其他),
然后 t.Run()使该函数文字在单独的goroutine中运行,如所记录的那样-在 expectedOutputcmpOpts.SingleApp以及其他不是 v(每次迭代的新变量)或 t(传递给函数文字)。

您可以运行 go test -race -run=TestIntegrationAppsWithProductionSelf ./...来查看参与的竞赛检测器使测试用例的代码崩溃。

关于go - 并行表驱动的go测试失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61100947/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com