-2

假设我有一个函数,如果它作为 go 例程异步执行:

func f(wg *sync.WaitGroup){
    defer wg.Done()
    // Do sth
}

main(){
    var wg sync.WaitGroup
    wg.Add(1)
    go f(&wg)
    wg.Wait() // Wait until f is done
    // ...
}

我将如何创建一个f确保wg.Done()被调用的单元测试?

一种选择是在调用wg.Done()之后直接调用测试f。如果f调用失败,wg.Done()测试会恐慌,这不好。另一种选择是创建一个接口,sync.WaitGroup但这似乎有点奇怪。

4

1 回答 1

1

如何为 f 创建一个单元测试以确保调用 wg.Done()?

像这样的东西:

func TestF(t *testing.T) {
    wg := &sync.WaitGroup{}
    wg.Add(1)

    // run the task asynchronously
    go f(wg)

    // wait for the WaitGroup to be done, or timeout
    select {
    case <-wrapWait(wg):
        // all good
    case <-time.NewTimer(500 * time.Millisecond).C:
        t.Fail()
    }
}

// helper function to allow using WaitGroup in a select
func wrapWait(wg *sync.WaitGroup) <-chan struct{} {
    out := make(chan struct{})
    go func() {
        wg.Wait()
        out <- struct{}{}
    }()
    return out
}

您不直接检查 WaitGroup ,无论如何您都不能这样做。相反,在给定预期输入的情况下,您断言该函数的行为符合预期。

在这种情况下,预期的输入是 WaitGroup 参数,预期的行为是wg.Done()最终被调用。这在实践中意味着什么?这意味着如果函数成功,计数为 1 的 WaitGroup 将达到 0 并允许wg.Wait()继续。

defer wg.Done()开头的声明f已经确保测试对错误或崩溃具有弹性。添加超时只是为了确保测试将在合理的时间内完成,即它不会让您的测试套件停顿太久。就个人而言,我更喜欢使用明确的超时,无论是使用计时器还是使用上下文,1)如果有人忘记在 CI 级别设置超时,可以避免出现问题,2)让任何签出 repo 并运行测试套件的人都可以使用时间上限,即避免依赖 IDE 配置或诸如此类的东西。

于 2021-11-20T21:29:03.683 回答