doubi9999 2016-11-15 17:03
浏览 106
已采纳

在Go中使用覆盖率信息测试OS.Exit方案(coveralls.io/Goveralls)

This question: How to test os.exit scenarios in Go (and the highest voted answer therein) sets out how to test os.Exit() scenarios within go. As os.Exit() cannot easily be intercepted, the method used is to reinvoke the binary and check the exit value. This method is described at slide 23 on this presentation by Andrew Gerrand (one of the core members of the Go team); the code is very simple and is reproduced in full below.

The relevant test and main files look like this (note that this pair of files alone is an MVCE):

package foo

import (
    "os"
    "os/exec"
    "testing"
)

func TestCrasher(t *testing.T) {
    if os.Getenv("BE_CRASHER") == "1" {
        Crasher() // This causes os.Exit(1) to be called
        return
    }
    cmd := exec.Command(os.Args[0], "-test.run=TestCrasher")
    cmd.Env = append(os.Environ(), "BE_CRASHER=1")
    err := cmd.Run()
    if e, ok := err.(*exec.ExitError); ok && !e.Success() {
        fmt.Printf("Error is %v
", e)
    return
    }
    t.Fatalf("process ran with err %v, want exit status 1", err)
}

and

package foo

import (
    "fmt"
    "os"
)

// Coverage testing thinks (incorrectly) that the func below is
// never being called
func Crasher() {
    fmt.Println("Going down in flames!")
    os.Exit(1)
}

However, this method appears to suffer certain limitations:

  1. Coverage testing with goveralls / coveralls.io does not work - see for instance the example here (the same code as above but put into github for your convenience) which produces the coverage test here, i.e. it does not record the test functions being run. NOTE that you don't need to those links to answer the question - the above example will work fine - they are just there to show what happens if you put the above into github, and take it all the way through travis to coveralls.io

  2. Rerunning the test binary appears fragile.

Specifically, as requested, here is a screenshot (rather than a link) for the coverage failure; the red shading indicates that as far as coveralls.io is concerned, Crasher() is not being called.

Coverage test showing Crasher() not being called

Is there a way around this? Particularly the first point.

At a golang level the problem is this:

  • The Goveralls framework runs go test -cover ..., which invokes the test above.

  • The test above calls exec.Command / .Run without -cover in the OS arguments

  • Unconditionally putting -cover etc. in the argument list is unattractive as it would then run a coverage test (as the subprocess) within a non-coverage test, and parsing the argument list for the presence of -cover etc. seems a heavy duty solution.

  • Even if I put -cover etc. in the argument list, my understanding is that I'd then have two coverage outputs written to the same file, which isn't going to work - these would need merging somehow. The closest I've got to that is this golang issue.


Summary

What I am after is a simple way to run go coverage testing (preferably via travis, goveralls, and coveralls.io), where it is possible to both test cases where the tested routine exits with OS.exit(), and where the coverage of that test is noted. I'd quite like it to use the re-exec method above (if that can be made to work) if that can be made to work.

The solution should show coverage testing of Crasher(). Excluding Crasher() from coverage testing is not an option, as in the real world what I am trying to do is test a more complex function, where somewhere deep within, under certain conditions, it calls e.g. log.Fatalf(); what I am coverage testing is that the tests for those conditions works properly.

  • 写回答

3条回答 默认 最新

  • dqhmtpuy94946 2016-11-25 09:36
    关注

    With a slight refactoring, you may easily achieve 100% coverage.

    foo/bar.go:

    package foo
    
    import (
        "fmt"
        "os"
    )
    
    var osExit = os.Exit
    
    func Crasher() {
        fmt.Println("Going down in flames!")
        osExit(1)
    }
    

    And the testing code: foo/bar_test.go:

    package foo
    
    import "testing"
    
    func TestCrasher(t *testing.T) {
        // Save current function and restore at the end:
        oldOsExit := osExit
        defer func() { osExit = oldOsExit }()
    
        var got int
        myExit := func(code int) {
            got = code
        }
    
        osExit = myExit
        Crasher()
        if exp := 1; got != exp {
            t.Errorf("Expected exit code: %d, got: %d", exp, got)
        }
    }
    

    Running go test -cover:

    Going down in flames!
    PASS
    coverage: 100.0% of statements
    ok      foo        0.002s
    

    Yes, you might say this works if os.Exit() is called explicitly, but what if os.Exit() is called by someone else, e.g. log.Fatalf()?

    The same technique works there too, you just have to switch log.Fatalf() instead of os.Exit(), e.g.:

    Relevant part of foo/bar.go:

    var logFatalf = log.Fatalf
    
    func Crasher() {
        fmt.Println("Going down in flames!")
        logFatalf("Exiting with code: %d", 1)
    }
    

    And the testing code: TestCrasher() in foo/bar_test.go:

    func TestCrasher(t *testing.T) {
        // Save current function and restore at the end:
        oldLogFatalf := logFatalf
        defer func() { logFatalf = oldLogFatalf }()
    
        var gotFormat string
        var gotV []interface{}
        myFatalf := func(format string, v ...interface{}) {
            gotFormat, gotV = format, v
        }
    
        logFatalf = myFatalf
        Crasher()
        expFormat, expV := "Exiting with code: %d", []interface{}{1}
        if gotFormat != expFormat || !reflect.DeepEqual(gotV, expV) {
            t.Error("Something went wrong")
        }
    }
    

    Running go test -cover:

    Going down in flames!
    PASS
    coverage: 100.0% of statements
    ok      foo     0.002s
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

悬赏问题

  • ¥15 乘性高斯噪声在深度学习网络中的应用
  • ¥15 运筹学排序问题中的在线排序
  • ¥15 关于docker部署flink集成hadoop的yarn,请教个问题 flink启动yarn-session.sh连不上hadoop,这个整了好几天一直不行,求帮忙看一下怎么解决
  • ¥30 求一段fortran代码用IVF编译运行的结果
  • ¥15 深度学习根据CNN网络模型,搭建BP模型并训练MNIST数据集
  • ¥15 C++ 头文件/宏冲突问题解决
  • ¥15 用comsol模拟大气湍流通过底部加热(温度不同)的腔体
  • ¥50 安卓adb backup备份子用户应用数据失败
  • ¥20 有人能用聚类分析帮我分析一下文本内容嘛
  • ¥30 python代码,帮调试,帮帮忙吧