我的代码中有很多组件具有持久的go-routines,用于侦听触发操作的事件。大多数情况下,没有理由(在测试之外)他们在完成该操作后发回通知。
但是,我的单元测试正在使用sleep等待这些异步任务完成:
// Send notification event.
mock.devices <- []sparkapi.Device{deviceA, deviceFuncs, deviceRefresh}
// Wait for go-routine to process event.
time.Sleep(time.Microsecond)
// Check that no refresh method was called.
c.Check(mock.actionArgs, check.DeepEquals, mockFunctionCall{})
这似乎已经破了,但是我无法想出一个更好的解决方案,不会给非测试使用增加不合理的开销。我错过了一个合理的解决方案吗?
答案 0 :(得分:7)
惯用法是将done
频道与您的数据一起传递给工作人员。常规例程应该close
done
频道和您的代码应该等到频道关闭:
done := make(chan bool)
// Send notification event.
mock.devices <- Job {
Data: []sparkapi.Device{deviceA, deviceFuncs, deviceRefresh},
Done: done,
}
// Wait until `done` is closed.
<-done
// Check that no refresh method was called.
c.Check(mock.actionArgs, check.DeepEquals, mockFunctionCall{})
使用此模式,您还可以为测试实现超时:
// Wait until `done` is closed.
select {
case <-done:
case <-time.After(10 * time.Second):
panic("timeout")
}
答案 1 :(得分:5)
Soheil Hassas Yeganeh的解决方案通常是一种很好的方式,或者至少是类似的方式。但它是API的一个变化,它可以为调用者创建一些开销(虽然不多;如果调用者,调用者不会 通过Done
通道不需要它)。也就是说,有些情况下你不需要那种ACK系统。
我强烈推荐测试包Gomega来解决这类问题。它旨在与Ginkgo一起使用,但可以单独使用。它通过Consistently
和Eventually
匹配器提供出色的异步支持。
尽管如此,虽然Gomega与非BDD测试系统配合良好(并且可以很好地集成到testing
),但这是一件非常重要的事情,可以成为一种承诺。如果你只想要那一件,你可以编写自己的这些断言版本。我建议遵循Gomega的方法,即轮询而不仅仅是一次睡眠(这仍然会睡觉;如果不重新设计你的API,就无法解决这个问题)。
以下是如何观察测试中的内容。您可以创建一个辅助函数,如:
http://play.golang.org/p/qpdEOsWYh0
const iterations = 10
const interval = time.Millisecond
func Consistently(f func()) {
for i := 0; i < iterations; i++ {
f() // Assuming here that `f()` panics on failure
time.Sleep(interval)
}
}
mock.devices <- []sparkapi.Device{deviceA, deviceFuncs, deviceRefresh}
Consistently(c.Check(mock.actionArgs, check.DeepEquals, mockFunctionCall{}))
显然,您可以调整迭代次数和间隔以满足您的需求。 (Gomega使用1秒超时,每10ms轮询一次。)
Consistently
的任何实现的缺点是,无论你的超时,你都必须吃掉每次测试。但是,真的没办法解决这个问题。你必须决定多长时间才能发生。#34;如果可能的话,很高兴将您的测试转向检查Eventually
,因为这可以更快地成功。
Eventually
稍微复杂一些,因为你需要使用recover
来捕捉恐慌,直到它成功,但它并不太糟糕。像这样:
func Eventually(f func()) {
for i := 0; i < iterations; i++ {
if !panics(f) {
return
}
time.Sleep(interval)
}
panic("FAILED")
}
func panics(f func()) (success bool) {
defer func() {
if e := recover(); e != nil {
success = true
}
}()
f()
return
}
最终,这只是一个稍微复杂一点的版本,但它将逻辑包装成一个函数,因此它读得更好。