这让我疯狂。假设我有以下功能:
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
在指定的时间量后关闭其 channel 参数,而 wait
在 wg.Wait()
后关闭其 channel 参数。
问题在于,这不会提供 100% 的覆盖率,因为如果两者都准备好发送/接收,则选择案例是(伪)随机统一选择的。以下版本的 Map
没有这个问题,但如果上游 channel (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% 的覆盖率,所有测试都通过了(除此之外还有其他测试)。但这让我很恼火,我似乎无法编写一个两全其美的版本,如果上游 channel 没有关闭就不会阻塞,并且保证提供 100% 的测试覆盖率(不仅仅是可能)。
对我有什么启发吗?
P.S.,如果你很好奇为什么“如果上行 channel 没有关闭就不会阻塞”很重要,那这只是强制症的另一种表现。这个函数是导出的,这意味着如果客户端代码出现问题,我的代码也会出现问题。我希望它比第一个版本更有弹性。
最佳答案
编辑:我对这个答案进行了大量编辑,因为它仍然不正确。这似乎工作得很好。
好吧,我感觉很不好意思。当我看到这个的时候,我一定是筋疲力尽了。绝对有可能以确定性的方式遍历这些选择语句:
func TestMapCancel(t *testing.T) {
src := make(chan interface{})
quit := make(chan struct{})
done := make(chan struct{})
go func() {
defer close(done)
Map(quit, nil, src, double)
}()
close(quit)
select {
case <-done:
case <-time.After(100 * time.Millisecond):
t.Error("quitting pre-send failed")
}
src = make(chan interface{})
quit = make(chan struct{})
done = make(chan struct{})
go func() {
defer close(done)
Map(quit, nil, src, double)
}()
src <- 1
close(quit)
select {
case <-done:
case <-time.After(100 * time.Millisecond):
t.Error("quitting pre-send failed")
}
}
关于testing - 如何重写此 select 语句以保证 100% 的测试覆盖率?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38775223/