显示没有盲点的功能测试的覆盖范围

时间:2016-09-25 18:44:58

标签: go

我有一个生产golang代码和功能测试,它不是用golang编写的。功能测试运行编译二进制。我的生产代码的非常简化版本在这里:main.go

package main

import (
    "fmt"
    "math/rand"
    "os"
    "time"
)

func main() {
    rand.Seed(time.Now().UTC().UnixNano())
    for {
        i := rand.Int()
        fmt.Println(i)
        if i%3 == 0 {
            os.Exit(0)
        }
        if i%2 == 0 {
            os.Exit(1)
        }
        time.Sleep(time.Second)
    }
}

我想为我的功能测试构建覆盖率配置文件。为此,我添加了main_test.go文件内容:

package main

import (
    "os"
    "testing"
)

var exitCode int

func Test_main(t *testing.T) {
    go main()
    exitCode = <-exitCh
}

func TestMain(m *testing.M) {
    m.Run()
    // can exit because cover profile is already written
    os.Exit(exitCode)
}

修改main.go

package main

import (
    "flag"
    "fmt"
    "math/rand"
    "os"
    "runtime"
    "time"
)

var exitCh chan int = make(chan int)

func main() {
    rand.Seed(time.Now().UTC().UnixNano())
    for {
        i := rand.Int()
        fmt.Println(i)
        if i%3 == 0 {
            exit(0)
        }
        if i%2 == 0 {
            fmt.Println("status 1")
            exit(1)
        }
        time.Sleep(time.Second)
    }
}

func exit(code int) {
    if flag.Lookup("test.coverprofile") != nil {
        exitCh <- code
        runtime.Goexit()
    } else {
        os.Exit(code)
    }
}

然后我构建覆盖二进制文件:

go test -c -coverpkg=.  -o myProgram

然后我的功能测试运行这个覆盖二进制文件,如下所示:

./myProgram -test.coverprofile=/tmp/profile
6507374435908599516
PASS
coverage: 64.3% of statements in .

我构建了HTML输出,显示了覆盖范围:

$ go tool cover -html /tmp/profile -o /tmp/profile.html
$ open /tmp/profile.html

html coverage profile

问题

由于条件exit,方法if flag.Lookup("test.coverprofile") != nil永远不会显示100%的覆盖率。因此,os.Exit(code)行对我的覆盖结果来说是一个盲点,但事实上,功能测试会在此行上进行,此行应显示为绿色。

另一方面,如果我删除条件if flag.Lookup("test.coverprofile") != nil,则行os.Exit(code)将终止我的二进制文件而不构建覆盖率配置文件。

如何重写exit()main_test.go以显示覆盖没有盲点

首先想到的解决方案是time.Sleep()

func exit(code int) {
        exitCh <- code
        time.Sleep(time.Second) // wait some time to let coverprofile be written
        os.Exit(code)
    }
}

但它不是很好,因为在退出之前会导致生产代码变慢。

2 个答案:

答案 0 :(得分:3)

根据我们在评论中的对话,我们的覆盖率配置文件将永远不会包含该行代码,因为它永远不会被执行。

如果没有看到完整的代码,很难找到合适的解决方案,但是你可以采取一些措施来增加覆盖率而不会牺牲太多。

func Main和TestMain

GOLANG的标准做法是避免测试主应用程序入口点,以便大多数专业人员将其他功能提取到其他类中,以便轻松测试。

GOLANG测试框架允许您使用main函数测试应用程序,但在其中您可以使用TestMain函数,该函数可用于测试代码需要在主线程上运行的位置。以下是来自GOLANG Testing的小动作。

  

测试程序有时需要在测试之前或之后进行额外的设置或拆卸。有时还需要测试来控制在main线程上运行哪些代码。要支持这些和其他情况,如果测试文件包含函数:func TestMain(m *testing.M)

查看GOLANG Testing了解详情。

工作示例

以下是测试代码所有功能的示例(覆盖范围为93.3%,我们将100%覆盖)。我对你的设计做了一些改动,因为它不适合测试,但功能仍然相同。

package main

  

dofunc.go

import (
    "fmt"
    "math/rand"
    "time"
)

var seed int64 = time.Now().UTC().UnixNano()

func doFunc() int {
    rand.Seed(seed)
    var code int
    for {
        i := rand.Int()
        fmt.Println(i)
        if i%3 == 0 {
            code = 0
            break
        }
        if i%2 == 0 {
            fmt.Println("status 1")
            code = 1
            break
        }
        time.Sleep(time.Second)
    }
    return code
}
  

dofunc_test.go

package main

import (
    "testing"
    "flag"
    "os"
)

var exitCode int

func TestMain(m *testing.M) {
    flag.Parse()
    code := m.Run()
    os.Exit(code)
}

func TestDoFuncErrorCodeZero(t *testing.T) {
    seed = 2

    if code:= doFunc(); code != 0 {
        t.Fail()
    }
}

func TestDoFuncErrorCodeOne(t *testing.T) {
    seed = 3

    if code:= doFunc(); code != 1 {
        t.Fail()
    }
}
  

main.go

package main

import "os"

func main() {
    os.Exit(doFunc());
}

运行测试

如果我们使用封面配置文件构建我们的应用程序。

  

$ go test -c -coverpkg=. -o example

然后运行它。

  

$ ./example -test.coverprofile=/tmp/profile

运行测试

1543039099823358511
2444694468985893231
6640668014774057861
6019456696934794384
status 1
PASS
coverage: 93.3% of statements in .

所以我们看到我们得到93%的覆盖率,我们知道这是因为我们没有main的任何测试覆盖来解决这个问题我们可以为它编写一些测试(不是一个好主意)因为代码有os.Exit或者我们可以重构它所以它非常简单,功能很少,我们可以将它从测试中排除。

要从覆盖率报告中排除main.go文件,我们可以通过在tags文件的第一行放置标记注释来使用版本main.go

  

//+build !test

有关构建标志的更多信息,请访问以下链接:http://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool

这将告诉GOLANG该文件应该包含在构建过​​程中,其中存在标记构建,但NOT存在标记测试。

查看完整代码。

//+build !test

package main

import "os"

func main() {
    os.Exit(doFunc());
}

我们需要建立略有不同的覆盖应用程序。

  

$ go test -c -coverpkg=. -o example -tags test

运行它会是一样的。

  

$ ./example -test.coverprofile=/tmp/profile

我们收到以下报告。

1543039099823358511
2444694468985893231
6640668014774057861
6019456696934794384
status 1
PASS
coverage: 100.0% of statements in .

我们现在可以构建覆盖html了。

  

$ go tool cover -html /tmp/profile -o /tmp/profile.html

Coverage HTML Report

答案 1 :(得分:0)

在我的pkglint project中,我声明了一个包可见的变量:

var exit = os.Exit

在设置测试的代码中,我用特定于测试的功能将其覆盖,并且在拆除测试时,将其重置回os.Exit。

这是一个简单实用的解决方案,对我来说至少需要一年的广泛测试。我得到了100%的分支覆盖率,因为根本不涉及分支。