如何检查是不是在单元测试中调用了 sync.WaitGroup.Done()
Posted
技术标签:
【中文标题】如何检查是不是在单元测试中调用了 sync.WaitGroup.Done()【英文标题】:How to check if sync.WaitGroup.Done() is called in unit test如何检查是否在单元测试中调用了 sync.WaitGroup.Done() 【发布时间】:2021-12-30 22:10:51 【问题描述】:假设我有一个函数,如果它作为 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()
?
一种选择是在调用f
之后直接在测试中调用wg.Done()
。如果f
调用wg.Done()
失败,测试将出现恐慌,这并不好。
另一种选择是为sync.WaitGroup
创建一个接口,但这似乎有点奇怪。
【问题讨论】:
事实上,1.18 似乎确实从 1.17 改变了 WaitGroup 的结构,所以如果你确实使用上面的建议,然后在 2021 年初升级到 1.18...他们的测试是有保证的去炸掉。 1.17 和 current master 如果 Done 没有被调用,那么 Wait 不会返回并且测试会被破坏。您不需要检查 WaitGroup 的内部结构。 因为这就是您测试此代码的方式。在单元测试中使用 goroutine 并没有错,它正在测试作为 goroutine 的代码。并发是语言使用的基础,而不是仅用于集成的东西。 @RasmusN 你是在这里向后弯腰的人——首先要避免在测试中使用 goroutines(可能是因为你在其他语言中的并发经验让你认为它是要远离的东西单元测试),然后与 JimB 争论,而不是从他们的贡献中学习。放手去吧。让 JimB 这样有经验的人指导您。 除非您解决了停机问题,否则您无法证明任何一组测试都会完成。为什么你计划让测试经常失败?我们总是在函数开始时精确地推迟 Done,因此它永远不会被跳过,并且在审查时将其删除是显而易见的。如果您以某种方式跳过它,则测试会超时一次,然后您就可以修复它。 【参考方案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()
继续。
f
开头的声明defer wg.Done()
已经确保测试对错误或崩溃具有弹性。添加超时只是为了确保测试将在合理的时间内完成,即它不会让您的测试套件停顿太久。就个人而言,我更喜欢使用明确的超时,无论是使用计时器还是使用上下文,以 1)避免在有人忘记在 CI 级别设置超时时出现问题,2)让任何签出 repo 并运行测试套件的人都可以使用时间上限,即避免依赖于 IDE 配置或诸如此类的东西。
【讨论】:
除了向out
频道发送内容,您也可以直接关闭它以上是关于如何检查是不是在单元测试中调用了 sync.WaitGroup.Done()的主要内容,如果未能解决你的问题,请参考以下文章
Unity 单元测试 - 检查带有参数的函数调用是不是引发异常