go - 显示无盲点的功能测试覆盖率

标签 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 方法永远不会显示 100% 的覆盖率,因为 if flag.Lookup("test.coverprofile") != nil。所以行 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)
    }
}

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

最佳答案

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

如果没有看到您的完整代码,就很难想出一个合适的解决方案,但是您可以做一些事情来增加覆盖率而不会牺牲太多。

func Main 和 TestMain

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

GOLANG 测试框架允许您在没有主函数的情况下测试您的应用程序,但您可以在其中使用 TestMain 函数,该函数可用于测试代码需要在主函数上运行的位置线。下面是 GOLANG Testing 的一个小例子。

It is sometimes necessary for a test program to do extra setup or teardown before or after testing. It is also sometimes necessary for a test to control which code runs on the main thread. To support these and other cases, if a test file contains a function: func TestMain(m *testing.M)

查看 GOLANG Testing 了解更多信息。

工作示例

下面是一个测试代码所有功能的示例(覆盖率为 93.3%,我们将其设为 100%)。我对您的设计做了一些更改,因为它不太适合测试,但功能仍然相同。

主要包

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 该文件应该包含在存在标签 build 的构建过程中,但NOT 存在标签 test 的地方。

查看完整代码。

//+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

关于go - 显示无盲点的功能测试覆盖率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39690509/

相关文章:

Go环境变量设置后保持不变

go - 在 Go 中初始化空字符串的惯用方法

go - 开始-指向 bool * bool的指针的合理性是什么?

Golang 不能使用类型作为参数中的类型

c - 为什么Golang在Linux上使用libc

pointers - golang 中的 slice 杂耍

go - 如何查找两个 slice 是否引用同一内存?

go - 使用 GOLANG V2 API 在 Google BigQuery 中进行参数化查询

go - 这两种声明方式之间的区别,一种是新声明,另一种是没有声明?

mongodb - 在命令提示符下将git与mongodb一起使用时出现问题