在Go中测试没有睡眠的异步结果 [英] Testing for asynchronous results without sleep in Go

查看:123
本文介绍了在Go中测试没有睡眠的异步结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的代码中有很多组件具有持久的go-routine,它们监听事件以触发操作。大多数时候,没有理由(测试之外)让他们在完成该操作后发回通知。



然而,我的单元测试使用睡眠等待这些异步任务完成:

  //发送通知事件。 
mock.devices< - [] sparkapi.Device {deviceA,deviceFuncs,deviceRefresh}

//等待go-routine处理事件。
time.Sleep(time.Microsecond)

//检查没有刷新方法被调用。
c.Check(mock.actionArgs,check.DeepEquals,mockFunctionCall {})

似乎打破了,但我还没有能够提出一个更好的解决方案,不会给非测试用法增加不合理的开销。有没有合理的解决方案,我已经错过了?

解决方案

Soheil Hassas Yeganeh的解决方案通常是一个好方法,或者至少像这样。但它是对API的改变,它可以为调用者创建一些开销(虽然不是很多;调用者不需要 来传递 Done 频道,如果调用者不需要它)。也就是说,有些情况下你不需要这种ACK系统。



我强烈推荐测试软件包 Gomega 就是这样一个问题。它旨在与银杏一起使用,但可以单独使用。它包含通过 Consistently 最终匹配器的出色异步支持。



这就是说,虽然Gomega在非BDD测试系统上运行良好(并且可以很好地集成到 testing 中),但这是一件非常重要的事情,可以作为承诺。如果你只是想要这一块,你可以编写自己的版本的这些断言。我建议遵循Gomega的方法,即投票而不仅仅是单一的睡眠(这仍然在睡觉;如果不重新设计你的API,不可能解决这个问题)。



以下是如何观察测试中的事情。你可以创建一个辅助函数,如:

http: //play.golang.org/p/qpdEOsWYh0

 常量迭代= 10 
const interval = time.Millisecond

func一致(f func()){
for i:= 0;我<迭代; i ++ {
f()//假设这里`f()`在失败时发生混乱
time.Sleep(间隔)
}
}

模拟.devices< - [] sparkapi.Device {deviceA,deviceFuncs,deviceRefresh}
一致地(c.Check(mock.actionArgs,check.DeepEquals,mockFunctionCall {}))

显然,您可以调整迭代次数和时间间隔以满足您的需求。 (Gomega使用1秒超时,每10ms进行一次轮询)。 $ b $ >一致的缺点是无论您何时超时,您都必须进行每次测试。但是真的没有办法。你必须决定足够长的时间不会发生。如果可能的话,最好转过来检查最终,因为这样可以更快地成功。



最后有点复杂,因为你需要使用 recover 来捕捉恐慌,直到它成功为止,但它是不错。就像这样:

  func最后(f func()){
for i:= 0;我<迭代; i ++ {
if!panics(f){
return
}
time.Sleep(interval)
}
panic(FAILED)


func panics(f func())(success bool){
推迟func(){
if e:= recover(); e!= nil {
success = true
}
}()
f()
return
}

最终,这只是您所拥有的稍微复杂一点的版本,但它会将逻辑封装到一个函数中,使其读取更好一点。

I have quite a few components in my code that have persistent go-routines that listen for events to trigger actions. Most of the time, there is no reason (outside of testing) for them to send back a notification when they have completed that action.

However, my unittests are using sleep to wait for these async tasks to complete:

// 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{})

That seems broken, but I haven't been able to come up with a better solution that doesn't add unreasonable overhead to non-test usage. Is there a reasonable solution that I've missed?

解决方案

Soheil Hassas Yeganeh's solution is usually a good way to go, or at least something like it. But it is a change to the API, and it can create some overhead for the caller (though not much; the caller doesn't have to pass a Done channel if the caller doesn't need it). That said, there are cases where you don't want that kind of ACK system.

I highly recommend the testing package Gomega for that kind of problem. It's designed to work with Ginkgo, but can be used standalone. It includes excellent async support via the Consistently and Eventually matchers.

That said, while Gomega works well with non-BDD test systems (and integrates fine into testing), it is a pretty big thing and can be a commitment. If you just want that one piece, you can write your own version of these assertions. I recommend following Gomega's approach though, which is polling rather than just a single sleep (this still sleeps; it isn't possible to fix that without redesigning your API).

Here's how to watch for things in testing. You create a helper function like:

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{}))

Obviously you can tweak iterations and interval to match your needs. (Gomega uses a 1 second timeout, polling every 10ms.)

The downside of any implementation of Consistently is that whatever your timeout, you have to eat that every test run. But there's really no way around that. You have to decide how long is long enough to "not happen." When possible, it's nice to turn your test around to check for Eventually, since that can succeed faster.

Eventually is a little more complicated, since you'll need to use recover to catch the panics until it succeeds, but it's not too bad. Something like this:

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
}

Ultimately, this is just a slightly more complicated version of what you have, but it wraps the logic up into a function so it reads a bit better.

这篇关于在Go中测试没有睡眠的异步结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆