【发布时间】:2016-12-11 01:00:45
【问题描述】:
这让我发疯了。假设我有以下功能:
func Map(quit <-chan struct{}, dst chan<- interface{}, src <-chan interface{}, f func(interface{}) interface{} {
for {
select {
case v, ok := <- src:
if !ok {
return
}
select {
case dst <- f(v):
case <-quit:
return
}
case <-quit:
return
}
}
}
对于从 src 接收到的每个值 v,它在 dst 上发送 f(v),直到 src 或 quit 关闭并为空或从 quit 接收到值。
现在,假设我想编写一个测试来证明它可以被取消:
func TestMapCancel(t *testing.T) {
var wg sync.WaitGroup
quit := make(chan struct{})
success := make(chan struct{})
wg.Add(3)
src := // channel providing arbitrary values until quit is closed
dst := make(chan interface{})
// mapper
go func() {
defer wg.Done()
defer close(dst)
Map(quit, dst, src, double)
}()
// provide a sink to consume values from dst until quit is closed
timeout(quit, 10*time.Millisecond)
wait(success, &wg)
select {
case <-success:
case <-time.After(100 * time.Millisecond):
t.Error("cancellation timed out")
}
}
这里的未定义函数并不是非常重要。请假设它们有效。 timeout 在指定时间后关闭其通道参数,wait 在wg.Wait() 后关闭其通道参数。
问题在于,这不会提供 100% 的覆盖率,因为如果两者都准备好发送/接收,则会以(伪)随机方式统一选择一个选择案例。以下版本的Map 没有此问题,但如果上游通道(src)未关闭,则可能会出现无限期阻塞:
func Map(quit <-chan struct{}, dst chan<- interface{}, src <-chan interface{}, f func(interface{}) interface{}) {
for v := range src {
select {
case dst <- f(v):
case <-quit:
return
}
}
}
我可以通过重写测试以在循环中重复几次来解决这个问题,这样每个分支都有机会被随机选择。我尝试了少至 10 次迭代,并在所有测试通过的情况下达到了 100% 的覆盖率(除此之外还有其他测试)。但它让我大吃一惊,我似乎无法编写一个两全其美的版本,如果上游通道没有关闭,它不会阻塞,并且保证提供 100% 的测试覆盖率(不仅仅是可能)。
对我有什么启发吗?
P.S.,如果您很好奇为什么“如果上游通道未关闭则不阻塞”很重要,这只是强迫症的另一种表现。该函数被导出,这意味着如果客户端代码行为不端,我的代码行为不端。我希望它比第一个版本更有弹性。
【问题讨论】:
标签: testing go code-coverage channel