使用package global时Go测试之间的竞争条件

ffx8fchx  于 2023-09-28  发布在  Go
关注(0)|答案(1)|浏览(92)

我使用一个包全局变量来保存包中的函数要使用的http客户端。这是为了让它可以被用于单元测试的模拟HTTP客户端所取代。
每个测试函数创建一个适当的模拟HTTP客户端示例,并在测试主体的其余部分之前将其分配给包全局。
测试主体运行启动引用全局的长寿goroutine的代码。当测试函数完成时,这个goroutine仍然在运行。
go测试框架启动遵循相同模式的下一个测试函数。当第一个goroutine读取刚刚写入第二个函数的mock http客户端示例的global时,会检测到竞态条件。
这是因为测试框架在第一个测试函数的同一个进程中运行第二个测试函数,所以长时间运行的goroutine不会被杀死。
有哪些解决方案?我非常希望不要仅仅为了提高可测试性的这个特定方面而在长时间运行的goroutine框架中添加“stop”语义。

7bsow1i6

7bsow1i61#

当为引用包全局状态的长寿goroutine编写测试时,您所描述的情况是一个常见的挑战。虽然完全避免在同一进程中的测试函数之间共享状态可能具有挑战性,但您可以使用一些策略来解决此问题,而无需更改框架或引入“停止”语义:

**使用Test Setup和Teardown函数:您可以使用测试包提供的TestMain函数来为您的测试设置和拆除共享资源,而不是直接修改包的全局变量。您可以在运行任何测试之前,在TestMain**函数中创建并分配模拟HTTP客户端示例,以确保每个测试都从一个干净的开始。
**创建全局变量的 Package 器:**将package-global变量 Package 在一个函数中,管理其状态和赋值。这可以帮助您更好地控制全局变量的访问和赋值。例如:

var httpClientMu sync.Mutex
var httpClient *http.Client

func SetHTTPClient(client *http.Client) {
    httpClientMu.Lock()
    defer httpClientMu.Unlock()
    httpClient = client
}

func GetHTTPClient() *http.Client {
    httpClientMu.Lock()
    defer httpClientMu.Unlock()
    return httpClient
}

然后,在测试中,使用**SetHTTPClient设置mock HTTP客户端,并使用GetHTTPClient**访问代码中的全局变量。此方法添加了一个同步级别以防止并发访问问题。

**使用依赖注入方法:**不依赖于包全局状态,而是重构代码以接受HTTP客户端作为相关函数或方法的参数。这使得代码更易于测试,并消除了对package-global变量的需要。
**考虑上下文:如果长寿goroutine正在发出HTTP请求,请考虑将context.Context传递给您的函数和goroutine。这允许您控制goroutine的生命周期,并在测试结束时取消它。使用context**包可以帮助管理长期操作。

虽然这些方法可能需要对代码进行一些调整,但它们可以帮助提高可测试性并避免测试函数之间共享状态的问题。在保持代码质量和提高可测试性之间取得平衡,同时在测试框架的约束下工作,这一点很重要。

相关问题