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

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

对于从 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 参数,而 waitwg.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/

相关文章:

haskell - Haskell 中的编译器集成测试

testing - 如果你有一个功能的场景/测试,你是否必须测试相反的?

Golang : how to mock . ..interface{} arguents 使用 gomock

unit-testing - 分支覆盖和断言覆盖?

azure - 200k RPM 的 jMeter 压力测试

javascript - 使用 page.evalute() 返回 DOM 元素

templates - 错误 : "invalid type for comparison" in revel template

go - 在没有结构的情况下解码未知的 JSON 字段

integration-testing - 在 SONAR 中使用 Jacoco 的 IT 测试覆盖率为 0%

javascript - 什么是 JavaScript 单元测试的代码覆盖率分支