测试

    覆盖率是一个术语,用于统计通过运行程序包的测试多少代码得到执行。 如果执行测试套件导致 80%的语句得到了运行,则测试覆盖率为 80%。

    计算测试覆盖率的通常方法是埋点二进制可执行文件。 例如,GNU gcov 在二进制文件中设置执行分支断点。 当每个分支执行时,断点被清除,并且分支的目标语句被标记为 “被覆盖”。

    这种方法是成功和广泛使用的。 Go 的早期测试覆盖工具甚至以相同的方式工作。但它有问题。 由于分析二进制文件的执行是很困难的,所以很难实现。 它还需要将执行跟踪绑定回源代码的可靠方法,这也可能是困难的。 那里的问题包括不正确的调试信息和诸如内联功能的问题, 使分析变得复杂。 最重要的是,这种方法非常不具有可移植性。 对于每个机器架构需要重新编写,在某种程度上, 可能对于每个

    操作系统

    都需要重新编写,因为从系统到系统的调试支持差异很大。

    Go 1.2 的发布引入了一个 test coverage 的新工具, 它采用了一种不寻常的方式来生成覆盖率统计数据,这种方法建立在 Godoc 的

    技术

    的基础上。

    1 Go 的测试覆盖率

    对于 Go 的新测试覆盖工具,采取了一种避免动态调试的不同方法。 想法很简单:在编译之前重写包的源代码,以埋点,编译和运行修改的源,并转储统计信息。 重写很容易编排,因为 go 的工具链 控制从源到测试到执行的整个流程。

    示例代码如下:

    func Size(a int) string {

    switch {

    case a < 0:

    return “negative”

    case a == 0:

    return “zero”

    case a < 10:

    return “small”

    case a < 100:

    return “big”

    case a < 1000:

    return “huge”

    }

    return “enormous”

    }

    测试代码如下:

    type Test struct {

    in int

    out string

    }

    var tests = []Test{

    {-1, “negative”},

    {5, “small”},

    }

    func TestSize(t *testing.T) {

    for i, test := range tests {

    size := Size(test.in)

    if size != test.out {

    t.Errorf(“#%d: Size(%d)=%s; want %s”, i, test.in, size, test.out)

    }

    }

    }

    cd ../src/cover/size/

    go test -cover

    cd -

    PASS

    coverage: 42.9% of statements

    ok _/home/parallels/program/org/github-pages/source/src/cover/size0.001s

    /home/parallels/program/org/github-pages/source/_posts

    启用测试覆盖后,/go test/ 运行 cover 工具,在编译之前重写源代码。 以下是重写后的 Size 函数: +

    func Size(a int) string {

    GoCover.Count[0] = 1

    switch {

    case a < 0:

    GoCover.Count[2] = 1

    return “negative”

    case a == 0:

    GoCover.Count[3] = 1

    return “zero”

    case a < 10:

    GoCover.Count[4] = 1

    return “small”

    case a < 100:

    GoCover.Count[5] = 1

    return “big”

    case a < 1000:

    GoCover.Count[6] = 1

    return “huge”

    }

    GoCover.Count[1] = 1

    return “enormous”

    }

    上面示例的每个可执行部分用赋值语句进行注解,赋值语句用于在运行时做统计。 计数器与 cover 工具生成的第二个只读数据结构

    记录

    的语句的原始源位置相关联。 测试运行完成后,收集计数器,通过查看设置的数量的来计算百分比。

    虽然分配注解看起来可能很昂贵,但是它被编译为单个 “移动” 指令。 因此,其运行时开销不大,运行典型(或更实际)测试时只增加约 3%开销。 这使得把测试覆盖率作为标准开发流程的一部分是合情合理的。

    2 查看结果

    上面的例子的测试覆盖率很差。 为了探索具体为什么,需要 go test 写一个 coverage profile , 这是一个保存收集的统计信息的文件,以便能详细地研究覆盖的细节。 这很容易做:使用 -coverprofile 标志来指定输出的文件:

    cd ../src/cover/size/

    go test -coverprofile=size_coverage.out

    注: -coverprofile 标志自动设置 -cover 来启用覆盖率分析。

    测试与以前一样运行,但结果保存在文件中。 要研究它们,需要运行 test coverage tool 。 一开始,可以要求 覆盖率 按函数分解,虽然在当前情况下没有太多意义,因为只有一个函数:

    cd ../src/cover/size/

    go tool cover -func=size_coverage.out

    查看的更有趣的方式是获取 覆盖率信息注释的源代码 的 HTML 展示。 该显示由 -html 标志调用:

    cd ../src/cover/size/

    go tool cover -html=size_coverage.out

    运行此命令时,

    浏览器

    将弹出窗口,已覆盖(绿色),未覆盖(红色)和 未埋点(灰色)。 下面是一个屏幕截图:

    Go的测试覆盖率 - 51Testing软件测试网 - 图1

    有了这个信息页,问题变得很明显:上面忽略了几个 case 的测试! 可以准确地看出具体是哪一个,这样可以轻松地提高的测试覆盖率。

    3 热力图

    源代码级方式来测试覆盖率的一大优点在于,可以很容易用不同的方式对代码进行埋点处理。 例如,不仅可以检测是否已执行了一个语句,而且还可以查询执行了多少次。

    go test 命令接受 -covermode 标志将覆盖模式设置为三种设置之一:

    set: 每个语句是否执行?

    count: 每个语句执行了几次?

    atomic: 类似于 count, 但表示的是并行程序中的精确计数

    set 是默认设置,上面示例已经看到了。 只有运行并行算法需要精确的计数时,才需要进行 atomic 设置。 它使用来自 sync/atomic 包的原子操作,这可能会相当昂贵。 然而,对于大多数情况, count 模式工作正常,并且像默认设置模式一样非常快。

    下面来试试一个标准包, fmt 格式化包语句执行的计数。 进行测试并写出 coverage profile ,以便能够很好地进行信息的呈现。

    go test -covermode=count -coverprofile=../src/cover/count.out fmt

    这比以前的例子好的测试覆盖率。 (覆盖率不受覆盖模式的影响)可以显示函数细节:

    go tool cover -func=../src/cover/count.out

    HTML 输出产生了巨大的回报:

    go tool cover -html=../src/cover/count.out

    pad 函数如下所示:

    Go的测试覆盖率 - 51Testing软件测试网 - 图2

    注意绿色的强度是如何变化。 最明亮的绿色的代表较高的执行数; 较少灰暗的绿色代表较低的执行数。 甚至可以将鼠标悬停在语句上,以便在弹出的 tool tip 中提示实际计数。 test coverage 产生了关于函数执行的大量信息,在分析中很有用的信息。 +

    4 基础块

    你可能已经注意到,上一个示例中 / 有关于闭合大括号中间的行的计数 / 不是你所期望的那样。 这是因为一直以来 test coverage 都不是一个不精确的科学。 +

    这里发生的很值得解释。 我们希望覆盖注解由程序中的分支划分,当二进制文件在传统方法中被调用时,它们是分开的。 不过,通过重写源代码很难做到这一点,因为分支没有明确展示在源代码中。

    覆盖注解的作用是是埋点,通常由大括号来限定。 一般来说,使之工作正常是非常困难的。 所使用的算法的处理结果是闭合括号看起来像属于它配对的块,而开放大括号看起来像属于块之外。 一个更有趣的结果出现在如下的一个表达式中:

    f() && g()

    没有试图单独调用对 f 和 g 的调用,无论事实如何,它们总是看起来像是运行相同的次数。

    公平来说,即使 gcov 在这里也有麻烦。 该工具使机制正确,但呈现是基于行的,因此可能会错过一些细微差别。

    5 总结

    这是关于 Go 1.2 test coverage 故事。 具有有趣实现的新工具不仅可以实现测试覆盖率的统计,而且易于解释,甚至可以提取 profile 信息。

    测试是

    软件开发

    和的重要组成部分,/test coverage/ 为测试策略添加一个简单的标准。 走向前, test 和 cover 。

    上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编 (021-64471599-8017),我们将立即处理。
    http://www.51testing.com/html/18/n-3725318.html