【问题标题】:Cannot generate go coverage file无法生成 go 覆盖文件
【发布时间】:2018-12-30 18:05:55
【问题描述】:

我尝试在本文中运行 sytemTest: https://www.elastic.co/blog/code-coverage-for-your-golang-system-tests

所以请遵循提示 首先,我创建一个名为 main_test.go 的系统测试文件,如下所示:

func TestSystem(t *testing.T) {
    t.Logf("systemtest mod=%v", *SystemTest)
    if *SystemTest {
        t.Log("runing system test....")
        main()
    }
}

当这个单元测试被执行时,整个主函数都会被执行

然后我构建一个测试二进制文件:

go test -c -covermode=count -coverpkg ./... -o main.test

并在我的测试环境中执行测试二进制文件

./main.test -systemTest  -test.coverprofile ./coverage.cov

因为程序会监听并等待客户的请求,所以除非我手动退出,否则它不会退出,这意味着不会生成coverprofile

所以我启动了一个计时器以在 15 秒后停止程序... 但是当程序退出时,仍然没有生成coverprofile

如果test不调用main,则coverprofile可以正常生成

查看主函数

var mkrtExitWait sync.WaitGroup
var mkrtExitCode int
var mkrtRunning bool = false

func MKrtRun() int {
    mkrtExitWait.Add(1)
    mkrtRunning = true
    mkrtExitWait.Wait()
    return mkrtExitCode
}
func MKrtExit(code int) {
    if !mkrtRunning {
        os.Exit(code)
    } else {
        mkrtRunning = false
        mkrtExitCode = code
        mkrtExitWait.Done()
    }
}


func main() {
    // listen and serve code 
    ......

    if *SystemTest {  // a command flag 
        go func(){
            time.Sleep(time.Second * 10)
            MKrtExit(0)
        }()
    }
    MKrtRun()
}

我尝试了一些方法来生成覆盖率文件,但它不起作用:

  1. 发送客户端请求告诉测试服务器在程序运行时执行os.Exit(0)

  2. 发送客户端请求告诉测试服务器在程序运行时执行panic()

  3. 杀死进程强制退出程序

有什么问题?

如何生成覆盖率文件?

【问题讨论】:

  • 不要运行已编译的测试二进制文件,而是让go test -cover 完成工作。
  • @Volker 我的编译环境和测试环境在两台不同的机器上,测试二进制文件是在本地机器上编译的,但是在另一个远程服务器上运行
  • 为什么你会认为封面配置文件从一台机器到另一台机器不同?
  • 只是导致测试程序甚至无法在编译机上运行,​​运行测试依赖的微服务太多,编译机无法提供

标签: go test-coverage


【解决方案1】:

我找到了无法生成coverprofile的原因..

主协程由测试二进制启动,它将等待所有测试任务完成,最后生成覆盖文件。 主函数测试是其中一项任务,所以如果主函数执行os.Exit(0),将不会生成覆盖文件。 而在我的测试程序中,当主函数测试任务结束时,还有一些其他的测试任务要执行,其中一个被I/O事件阻塞而没有超时,所以它与主函数测试任务无关。

【讨论】:

  • 我遇到了同样的问题。您能否详细说明您的解决方案?
  • 我遇到了同样的问题。我从 TestMain() 中的 _test.go 文件调用 main()。我确保这是我的仓库中唯一的测试。但仍然没有报告。您能否详细说明您的解决方案?
  • 看我的回答,我从这个提示中详细阐述了
【解决方案2】:

为我解决了这个问题(并在@darkwing 的回答中提到)是标志顺序。

当我首先放置-test.coverprofile=coverage.cov 时,它起作用了,并且创建了coverage.cov 文件。

注意:我必须在标志名称和值之间添加= 符号。

我也不需要在不同的 goroutine 中运行它。

【讨论】:

    【解决方案3】:

    OP 暗示了一个答案,但并没有真正给出答案。关键是为了编写覆盖率配置文件,程序需要将执行返回到TestMain,而不能只调用os.Exit(0)

    所以真正需要做的就是在主 goroutine 中使用 return 处理一些代码。下面是示例代码,它在编译测试二进制文件后运行:

    go test -c -covermode=count -cover -coverpkg ./...

    然后它像这样运行(引号在 Windows 上的 powershell 中是必需的,但不是 cmd):

    testmain.test.exe "-test.coverprofile" coverage.cov systemTest

    我发现由于某种原因,仅当我的自定义标志 systemTest 出现在 -test.coverprofile coverage.cov 之后时才会生成覆盖文件。这是一个简单的示例:

    main.go:

    func main() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, os.Interrupt)
        done := make(chan struct{})
    
    
        go func() {
            <-c
            done <- struct{}{}
        }()
    
        go func() {
            for {
                // this is where your real program runs
                fmt.Println("printing and sleeping")
                time.Sleep(1 * time.Second)
            }
        }()
        <-done
        return
    }
    

    main_test.go:

    func TestMain(t *testing.T) {
        run := false
    
        for _, arg := range os.Args {
            switch {
            case arg == "systemTest":
                run = true
            }
        }
    
        if run {
            main()
            fmt.Println("returned from main")
        }
    }
    

    灵感:https://www.cyphar.com/blog/post/20170412-golang-integration-coveragehttps://www.elastic.co/blog/code-coverage-for-your-golang-system-tests

    【讨论】:

      【解决方案4】:

      系统测试通常用于在您的测试中运行一些外部测试时

      这意味着您需要修改主程序以在某些中断时退出。 SIGINT 是最适合您的主程序退出的信号,因为它确认符合 POSIX 信号合规性

      这可以通过添加以下来完成

              // Handle graceful shutdown of http server via OS Interrupt
              quit := make(chan os.Signal)
              signal.Notify(quit, os.Interrupt)
      
              // Block a goroutine on Interrupt channel and close
              // http server to exit gracefully
              go func() {
                  <-quit
                  log.Info("receive interrupt signal")
                  MKrtEixt(0)
              }() 
      

      那么你的测试步骤就是

      1. 调出被测系统
      2. 运行外部测试
      3. 测试完成后向被测系统发出 SIGINT
      4. 收集覆盖文件

      【讨论】:

      • 之前试过这个,但是没用,结果很奇怪:Issue SIGINT cannot stop the program。在 MKrtEixt(0) -> mkrtExitWait.Done() -> mkrtExitWait.Wait() -> main.go return 之后,我跟踪了每条执行的行,但程序仍在运行,我猜它与 test birnary 有关,如果我去运行 main .go , SIGINT 可以停止程序
      • os.Exit(0) 或 panic() 可以停止测试程序,但 MKrtEixt(0) 不能
      • 我猜你的MKrtEixt func 有一个错误!我一直在使用这种方法,除了我们必须停止 http 服务器而不是调用 os.Exit()
      • 我大概知道main() return 不能停止测试程序的原因,在构建测试二进制文件时,可能有另一个main() 函数被添加来执行TestSystem() 函数,TestSystem () 然后执行我定义的 main() 函数,也就是说我们看到的 main() 函数并不是主协程中运行的真正的 main() 函数,所以当它返回时,主协程没有任何作用,整个程序还在运行, 只有执行 os.Exit(0) 才能停止程序
      • 那么,你认为给出的答案是正确的吗?我建议编辑答案/问题以添加您的警告。它可能是其他人:-)
      猜你喜欢
      • 2021-12-02
      • 2021-10-08
      • 2011-06-06
      • 1970-01-01
      • 2019-11-05
      • 1970-01-01
      • 2022-07-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多