我的代码中有相当多的组件具有持久的GO routine ,它们侦听事件以触发操作.大多数情况下,他们没有理由(在测试之外)在完成该操作后发回通知.

但是,我的单元测试正在使用睡眠等待这些异步任务完成:

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

这似乎有问题,但我还没有想出一个更好的解决方案,它不会给非测试使用增加不合理的开销.我错过了什么合理的解决方案吗?

推荐答案

Soheil Hassas Yeganeh的解决方案通常是一个好办法,或者至少是类似的办法.但这是对API的一个更改,它可能会给调用方带来一些开销(虽然不是很大;如果调用方不需要Done个通道,调用方不需要它).也就是说,在某些情况下,你不想要那种ACK系统.

我强烈推荐针对这类问题的测试包Gomega.它是专为Ginkgo设计的,但可以独立使用.它包括通过ConsistentlyEventually匹配器提供出色的异步支持.

也就是说,虽然GoMega可以很好地与非BDD测试系统一起工作(并且可以很好地集成到testing中),但它是一件相当大的事情,可以是一项promise .如果您只需要这一部分,您可以编写这些断言的您自己的版本.不过,我建议遵循GoMega的方法,即轮询,而不仅仅是单一Hibernate (这仍然处于Hibernate 状态;如果不重新设计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来捕捉panic ,直到它成功,但这并不是太糟糕.大概是这样的:

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
}

最终,这只是您已有内容的一个稍微复杂的版本,但是它将逻辑包装到一个函数中,这样它的可读性会更好一些.

Go相关问答推荐

理解Golang并发:缓冲通道的意外行为

在GO中使用泛型类型 struct 实现接口方法

如果values.yaml文件中不存在某个属性,如何返回默认的FALSE?

Redis:尽管数据存在,但 rdb.Pipelined 中出现redis:nil错误

Kperf 构建失败

如何根据地址和大小打印字符串

如何为ANTLR4目标Go调试监听器

linter 警告:返回值被忽略

最长连续重复的字符golang

将 struct 转换为 CSV 字符串

如何使用管理员权限启动 powershell 进程并重定向标准输入 (os.exec)

如何使用名称具有包名称的嵌套 struct 启动 go struct

Wire google Inject with multi return from provider 函数

Go cmp - 如何为以 struct 为键的映射定义自定义相等性?

在 Golang 中,如何将接口作为泛型类型与 nil 进行比较?

我相信我正确地在 sRGB 和线性 RGB 之间进行了转换,那么为什么深色的结果看起来更糟呢?

如何在测试中使用自定义标志(使用`testify/suite`)

Go AST:获取所有 struct

Golang 有类似 C++ 的 decltype 的东西吗?

Golang 中的无实体函数