Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate unit test coverage when using command line flags in Golang subprocess testing?

I have unit tests for most of our code. But I cannot figure out how to generate unit tests coverage for certain code in main() in main package.

The main function is pretty simple. It is basically a select block. It reads flags, then either call another function/execute something, or simply print help on screen. However, if commandline options are not set correctly, it will exit with various error codes. Hence, the need for sub-process testing.

I tried sub-process testing technique but modified code so that it include flag for coverage:

cmd := exec.Command(os.Args[0], "-test.run=TestMain -test.coverprofile=/vagrant/ucover/coverage2.out")

Here is original code: https://talks.golang.org/2014/testing.slide#23 Explanation of above slide: http://youtu.be/ndmB0bj7eyw?t=47m16s

But it doesn't generate cover profile. I haven't been able to figure out why not. It does generate cover profile for main process executing tests, but any code executed in sub-process, of course, is not marked as executed.

I try to achieve as much code coverage as possible. I am not sure if I am missing something or if there is an easier way to do this. Or if it is just not possible.

Any help is appreciated.

Thanks

Amer

like image 624
Amer Avatar asked Sep 24 '14 16:09

Amer


3 Answers

I went with another approach which didn't involve refactoring main(): see this commit:

I use a global (unexported) variable:

var args []string

And then in main(), I use os.Args unless the private var args was set:

a := os.Args[1:]
if args != nil {
    a = args
}
flag.CommandLine.Parse(a)

In my test, I can set the parameters I want:

args = []string{"-v", "-audit", "_tests/p1/conf/gitolite.conf"}
main()

And I still achieve a 100% code coverage, even over main().

like image 141
VonC Avatar answered Oct 19 '22 20:10

VonC


I would factor the logic that needs to be tested out of main():

func main() {
    start(os.Args)
}

func start(args []string) {
    // old main() logic
}

This way you can unit-test start() without mutating os.Args.

like image 21
JimB Avatar answered Oct 19 '22 21:10

JimB


Using @VonC solution with Go 1.11, I found I had to reset flag.CommandLine on each test redefining the flags, to avoid a "flag redefined" panic.:

    for _, check := range checks {
        t.Run("flagging " + check.arg, func(t *testing.T) {
            flag.CommandLine = flag.NewFlagSet(cmd, flag.ContinueOnError)
            args = []string{check.arg}
            main()
        })
    }
like image 1
FGM Avatar answered Oct 19 '22 19:10

FGM