我在我的代码中有相当多的组件,它们具有持久的go-例程,用于侦听事件以触发操作。大多数情况下,他们没有理由(在测试之外)在完成该操作后发回通知。
然而,我的单元测试使用睡眠来等待这些异步任务完成:
// 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{})这看起来很糟糕,但我还没能想出一个更好的解决方案,它不会给非测试用法增加不合理的开销。有没有我错过的合理的解决方案?
发布于 2015-05-25 03:17:44
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实现的缺点都是,无论您的超时是什么,您都必须在每次测试运行时都接受它。但是确实没有办法绕过这一点。你必须决定多长时间才能“不发生”。在可能的情况下,最好将测试转向检查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
}最终,这只是您所拥有的内容的一个稍微复杂的版本,但它将逻辑包装到一个函数中,因此它的可读性更好。
发布于 2015-05-25 03:13:37
惯用的方法是将done通道与您的数据一起传递给worker go-例程。go-例程应该对done通道执行close操作,并且您的代码应该一直等到通道关闭:
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")
}https://stackoverflow.com/questions/30427013
复制相似问题