英文:
Is Test_xxx func safe to access shared data in golang?
问题
我对Golang的单元测试感到困惑。
我有两个Test_xxx
函数,比如Test_1
和Test_2
。
在Test_1
中,我会改变一个全局变量,Test_2
能看到这个变化吗?
此外,如果我使用monkey patch
而不是改变全局变量,其他的Test_xxx
函数能察觉到这个替换吗?
也就是说,当Test_xxx
返回时,我是否需要使用defer取消函数的替换?
英文:
I'm confused about the golang unit test.
I have 2 Test_xxx
funcs , like Test_1
and Test_2
.
In Test_1
, i will change a global variable , can Test_2
see the change?
Furthermore, if i use monkey patch
instead of changing global var, will the other Test_xxx
func perceive the patching?
i.e. do i have the necessary to cancel the func substitution using defer when Test_xxx
returns?
答案1
得分: 5
Test_xxx
函数在访问共享数据时是否安全取决于这些测试函数是否被允许并行运行。
默认情况下,go test
按顺序调用给定包中的测试函数。然而,如果:
- 在两个测试函数中都调用了
t.Parallel()
,并且 - 这两个函数在没有任何同步的情况下访问(写/写或写/读)相同的全局变量,
那么很可能会出现数据竞争。
为了更好地理解,考虑以下简单的测试文件:
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
这应该让你意识到在测试中处理全局状态时需要小心。最好的做法是,如果可能的话,完全避免使用全局状态。或者,记住一旦启用并行测试执行,必须小心同步对全局状态的访问。
英文:
> Is Test_xxx
func safe to access shared data in golang?
The answer entirely depends on whether those test functions are allowed to run in parallel.
By default, go test
calls the test functions for a given package sequentially. However, if
- you call
t.Parallel()
within both test functions, and - both functions access (write/write or write/read) the same global variable without any synchronization between them,
you'll likely get a data race.
To fix ideas, consider this simple test file:
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)
}
If you run go test -race
, the race detector will slap you on the wrist:
==================
WARNING: DATA RACE
--snip--
FAIL
exit status 1
FAIL whatever 0.730s
This should convince you that you should be careful about handling global state in tests. The best thing to do is to avoid global state altogether, if you can. Alternatively, remember that care must be taken to synchronize access to global state as soon as you activate parallel test execution.
答案2
得分: 2
在Test_1中,我将更改一个全局变量,Test_2能看到这个变化吗?
是的。
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
我是否有必要在Test_xxx返回时使用defer来取消函数替换?
你可以使用Cleanup函数来恢复全局变量的更改
func Test_1(t *testing.T) {
t.Cleanup(func() {
global = 0
})
for i := 0; i < 1000; i++ {
global++
}
fmt.Println(global)
}
英文:
> In Test_1, i will change a global variable , can Test_2 see the change?
Yes.
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)
}
Out
=== 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?
You can use Cleanup function to remove changes of global variable
func Test_1(t *testing.T) {
t.Cleanup(func() {
global = 0
})
for i := 0; i < 1000; i++ {
global++
}
fmt.Println(global)
}
<kbd>PLAYGROUND</kbd>
答案3
得分: 1
虽然这是可能的,但你可能希望考虑从两个测试中初始化全局变量,以生成一致的行为。当你从go
命令行运行测试时,你可以选择只运行一个测试函数(go test foo -test.run Test_1
),否则会生成不一致的结果。
访问全局变量会受到各种竞争条件的影响(在某些情况下,涉及到数据被其他地方覆盖时的部分读取),返回无意义/不可能的值!使用某种形式的sync.Mutex
来保护免受这些竞争条件的影响:
import (
"sync"
)
var mu sync.Mutex
var global int
func readGlobal() int {
mu.Lock()
defer mu.Unlock()
return global
}
func writeGlobal(val int) {
mu.Lock()
defer mu.Unlock()
global = val
}
// your test functions
英文:
While this is possible, you might want to consider initializing the global from both tests to generate consistent behavior. When you run the test from the go
command line you can choose to only run one test function (go test foo -test.run Test_1
) and this will otherwise generate inconsistent results.
Accessing global variables is subject to all sorts of races (in some cases involving partial reads of the data as it is being overwritten elsewhere) returning nonsense/impossible values! Use some sort of sync.Mutex
to protect from these races:
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
答案4
得分: 1
请注意,未来的 Go 版本可能会更改测试运行的顺序,例如通过随机化顺序。如果你的测试依赖于 Test_1
在 Test_2
之前运行并更改全局变量的事实,那么它将会出错。
一个良好的习惯是使用以下代码来更改全局变量:
func Test_1(t *testing.T) {
oldVal := myGlobalVariable
defer func() { myGlobalVariable = oldVal }()
// 其余的测试代码
}
英文:
Note that a future Go version may change the order in which the tests are run, e.g. by randomizing order. If your test depends on the fact that Test_1
runs before Test_2
and changes a global variable, it will break.
A good idiom for changing a global variable is something like:
func Test_1(t *testing.T) {
oldVal := myGlobalVariable
defer func() { myGlobalVariable = oldVal }
// rest of the test
}
答案5
得分: 1
在Test_1中,我将更改一个全局变量,Test_2能看到这个变化吗?
只有在一些特定条件下才能确保安全:
- 你在单个Goroutine中运行测试。你不能在测试中使用
t.Parallel()
。 - 你只能运行一次测试。否则,你必须实现一个额外的拆卸例程,在每次测试运行后将数据重置为其原始状态。
- 你不能改变文件中测试的顺序。开发人员习惯于依赖函数的顺序不重要。对顺序的依赖可能对将测试移动而不更改其代码的人非常困惑。
这些只是我脑海中的一些例子。违反这些条件将导致测试失败。这就是为什么这样的测试被称为脆弱测试。
检查是否可以避免这种情况。
通常这需要代码更改和引入新的模式,比如依赖注入。使代码具有可测试性是一件好事。它使代码更模块化,更易于维护。
英文:
> In Test_1, i will change a global variable , can Test_2 see the change?
It could be safe only under some specific conditions:
- You run your tests in a single Goroutine. You cannot use
t.Parallel()
in test. - You can run your tests only once. Otherwise, you have to implement an additional teardown routine to reset the data into its original state after every tests run.
- You cannot change tests order in the file(s). Developers used to rely on functions order being not important. Dependency on order could be very confusing for someone that moves the test without changing its code.
Those are just a few examples from the top of my head. Breaking any of these conditions will break a test. That is why such tests called fragile.
Check if that could be avoided.
Often that requires code change and introducing new patterns like Dependency Injection. Making code testable
is a good thing. You make it more modular and easier to maintain.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论