如何重写此select语句以保证100%的测试覆盖率?

时间:2016-08-04 18:51:15

标签: testing go code-coverage channel

这让我感到非常疯狂。假设我有以下功能:

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

它在dst上为从src接收的每个值v发送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在指定的时间后关闭其频道参数,waitwg.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。,如果你好奇为什么&#34;如果上游频道没有关闭则不会阻止&#34;很重要,它只是OCD的另一部分。导出此函数,这意味着如果客户端代码行为不正常,我的代码行为不端。我希望它比第一个版本更具弹性。

1 个答案:

答案 0 :(得分:0)

编辑:我对此答案进行了大量编辑,因为它仍然不正确。这看起来效果很好。

好的,所以我感觉非常羞怯。当我看着这个时,我一定是被烧掉了。绝对有可能以确定的方式遍历那些选择语句:

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