在涉及全局变量的不同包中同步测试的最佳方法是什么?

huangapple go评论91阅读模式
英文:

What is the best way to synchronize tests in different packages which involve with a global variable?

问题

我正在进行一个Go项目,我的任务是为一个包编写一些测试。这些测试需要访问不同包中的全局变量。这个全局变量可能在不同包的测试中进行设置/访问。因为默认情况下,Go测试会并行运行不同的包,所以对这个变量的设置/访问可能会创建竞态条件。

最简单的同步方法是创建一个跨越不同包的共享sync.Mutex。话虽如此,我尝试将这个sync.Mutex放在声明全局变量的xxx.go的xxx_test.go中,但不幸的是,由于作用域限制,另一个包无法访问这个Mutex。最终,我发现我必须将这个sync.Mutex放在一个专门用于测试的实用程序包中,这样不同的包就可以访问这个Mutex,以实现对全局变量的同步目的。

我对Go语言还不太熟悉。我只是觉得在测试中需要创建一个实用程序包来实现同步目的有些尴尬,另一方面,如果另一个开发人员在另一个包中进行一些测试,他/她可能不知道在哪里找到这个sync.Mutex。是否有更好的方法来实现这个同步目的,以及更优雅的解决方案?

非常感谢!

英文:

I'm working on a go project, my task is writing some tests for a package. The tests need to access a global variable in a different package. This global variable might be setup/access in different packages' testing. Because by default go testing runs in parallel for different packages. Therefore, setup/access to this variable might create race conditions.

The easiest way to synchronize is to create a shared sync.Mutex which spans in the different packages. Having said that, I tried to put this sync.Mutex in the xxx_test.go of xxx.go in which the global variable is declared, unfortunately, another package was not able to access this Mutex due to scope limitation. Eventually, I figured out I have to put this sync.Mutex to a utility package which is dedicated to testing, then different packages can access this Mutex to achieve sync purpose for that global variable.

I'm new to go lang. I just felt it's awkward that I need to create a utility package to just achieve sync purpose in the testing, on the other hand, if another developer does some testing in another package, he/she probably doesn't know where to find this sync.Mutex. Is there any better way to achieve this sync purpose with an elegant solution.

Many thanks!

答案1

得分: 2

这个问题非常广泛。关于这个主题已经有很多专著写过了。

但是既然你特别问到全局变量,最简单的答案是:不要使用全局变量。

我们一直被教导全局变量是一种反模式。测试是全局变量和单例模式成为反模式的主要原因之一。

如果可能的话,重构你的全局状态,至少允许使用实例变量,而不是全局变量。然后你可以将该实例传递给你的函数进行测试。

举个简单的例子,假设你有以下全局变量和一个使用它的函数:

var globalFoo = 123

func doFoo() string {
    return fmt.Sprintf("foo = %d", globalFoo)
}

修改你的函数,将变量作为参数传入:

func doFoo(foo int) string {
    return fmt.Sprintf("foo = %d", foo)
}

如果你的全局状态是数据库连接或大量变量,这可能会更复杂,但原则是相同的:不要使用全局变量,而是传递变量。

在一个遗留代码库中,一种在不改变函数签名的情况下允许测试的方法是创建简单的包装函数:

func doFoo() string {
    return realDoFoo(globalFoo)
}

func realDoFoo(foo int) string {
    fmt.Sprintf("foo = %s", foo)
}
英文:

This question is impossibly broad. Entire books have been written on the topic.

But since you asked specifically about global variables, the simplest answer is: Don't use global variables.

We're always taught that global variables are an anti-pattern. Testing is one of the main reasons that global variables and singletons is an anti-pattern.

If possible, refactor your global state to at least allow an instance variable, rather than a global. Then you can pass that instance into your functions for testing.

As a simplified example, suppose you have the following global variable, and a function which uses it:

var globalFoo = 123

func doFoo() string {
    return fmt.Sprintf("foo = %d", globalFoo)
}

Modify your function to take your variable as an argument:

func doFoo(foo int) string {
    return fmt.Sprintf("foo = %d", foo)
}

This can be more complicated if your global state is a database connection, or a large number of variables, but the principle is the same: Don't use globals; pass variables instead.

In a legacy codebase, one method to allow testing, without changing your function signatures, is to create simple wrapping functions:

func doFoo() string {
    return realDoFoo(globalFoo)
}

func realDoFoo(foo int) string {
    fmt.Sprintf("foo = %s", foo)
}

huangapple
  • 本文由 发表于 2017年8月16日 18:25:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/45711053.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定