Test_xxx func 在 golang 中访问共享数据是否安全?

Is Test_xxx func safe to access shared data in golang?

我对 golang 单元测试感到困惑。

我有 2 个 Test_xxx 函数,比如 Test_1Test_2.
Test_1 中,我将更改一个 global 变量,Test_2 可以看到更改吗?

此外,如果我使用 monkey patch 而不是更改 global var,其他 Test_xxx func 会感知补丁吗?
即我是否有必要在 Test_xxx returns?

时使用 defer 取消函数替换

In Test_1, i will change a global variable , can Test_2 see the change?

是的。

var global = 0

func Test_1(t *testing.T) {
    for i := 0; i < 1000; i++ {
        global++
    }
    fmt.Println(global)
}

func Test_2(t *testing.T) {
    for i := 0; i < 1000; i++ {
        global++
    }
    fmt.Println(global)
}

出来

=== RUN   Test_1
1000
--- PASS: Test_1 (0.00s)
=== RUN   Test_22
2000
--- PASS: Test_22 (0.00s)
PASS

do i have the necessary to cacel the func substition using defer when Test_xxx returns?

可以使用Cleanup函数去除全局变量的变化

func Test_1(t *testing.T) {
    t.Cleanup(func() {
        global = 0
    })
    for i := 0; i < 1000; i++ {
        global++
    }
    fmt.Println(global)
}

PLAYGROUND

Is Test_xxx func safe to access shared data in golang?

答案完全取决于是否允许这些测试函数运行并行。

默认情况下,go test 按顺序调用给定包的测试函数。但是,如果

  • 您在两个测试函数中都调用了 t.Parallel(),并且
  • 两个函数都访问(write/write 或 write/read)同一个全局变量,它们之间没有任何同步,

您可能会遇到数据竞争。


要修正想法,请考虑这个简单的测试文件:

package main

import (
    "fmt"
    "testing"
)

var count = 0

func Test_1(t *testing.T) {
    t.Parallel()
    count++
    fmt.Println(count)
}

func Test_2(t *testing.T) {
    t.Parallel()
    count++
    fmt.Println(count)
}

如果你运行go test -race,种族检测器会拍打你的手腕:

==================
WARNING: DATA RACE
--snip--
FAIL
exit status 1
FAIL    whatever    0.730s

这应该使您相信在测试中处理全局状态时应该小心。如果可以的话,最好的办法是 avoid global state altogether。或者,请记住,一旦激活并行测试执行,就必须注意同步对全局状态的访问。

虽然这是可能的,但您可能需要考虑从两个测试中初始化全局以生成一致的行为。当您从 go 命令行 运行 测试时,您可以选择仅 运行 一个测试函数 (go test foo -test.run Test_1),否则会产生不一致的结果。

访问全局变量会受到各种竞争的影响(在某些情况下涉及部分读取数据,因为它正在其他地方被覆盖)返回 nonsense/impossible 值!使用某种 sync.Mutex 来防止这些种族:

import (
    "sync"
)

var mu sync.Mutex
var global int

func readGlobal() int {
    mu.Lock()
    defer mu.Unock()
    return global
}

func writeGlobal(val int) {
    mu.Lock()
    defer mu.Unock()
    global = val
}

// your test functions

请注意,未来的 Go 版本可能会更改测试的顺序 运行,例如通过随机顺序。如果您的测试依赖于 Test_1 运行s 在 Test_2 之前并更改全局变量这一事实,它将中断。

更改全局变量的一个很好的习惯用法是这样的:

func Test_1(t *testing.T) {
  oldVal := myGlobalVariable
  defer func() { myGlobalVariable = oldVal }
  // rest of the test
}

In Test_1, i will change a global variable , can Test_2 see the change?

只有在某些特定条件下才是安全的:

  1. 您 运行 在单个 Goroutine 中进行测试。您不能在测试中使用 t.Parallel()
  2. 您只能 运行 测试一次。否则,您必须在每次测试 运行.
  3. 后实施额外的拆卸例程以将数据重置为原始状态
  4. 您不能更改文件中的测试顺序。开发人员过去常常依赖于功能顺序并不重要。对于在不更改代码的情况下移动测试的人来说,对顺序的依赖可能会非常混乱。

这些只是我脑海中的几个例子。打破这些条件中的任何一个都会破坏测试。这就是为什么这样的测试称为脆弱。

检查是否可以避免这种情况。

通常这需要更改代码并引入新模式,例如 Dependency Injection。制作代码 testable 是一件好事。你让它更模块化,更容易维护。