Golang os.exec
我们进行Linux后端服务编程时,经常会进行系统操作。Golang对系统命令执行的支持是 通过os.exec库实现的。
那么在测试时,我们如何在UT当中,对调用os.exec的地方进行单元测试呢?
这个库的mock并不好处理,在设置exec.Command之后执行它的run方法,所以即使想对它 进行打桩也找不到可以替换的接口和mock对象。
这个包自己的解决办法
在exec这个包的测试文件exec_test.go中,可以看到有这样一段代码: ```Go func helperCommand(t *testing.T, s ...string) *exec.Cmd {
cs := []string{"-test.run=TestHelperProcess", "--"}
cs = append(cs, s...)
cmd := exec.Command(os.Args[0], cs...)
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
return cmd } ``` 上面这个函数在测试用例执行时执行的,并且在函数中执行一个命令为os.Args[0]的系统命令。 os.Args[0]就是这个Go代码在执行go test的时候的执行程序。 通过上面这个代码,go test执行到这个函数时,就会以```"-test.run=TestHelperProcess -- "``` 为参数执行go test执行程序。go test的可执行文件,执行机制是执行 test.run指定的测试用例。 那么现在就相当与指定执行```TestHelperProcess```这个用例了。 详细解释见:[测试 os/exec.Command](https://npf.io/2015/06/testing-exec-command/)
应用实例
- 第一步,需要把产品代码中对os.exec的调用做个封装:
var execCommand = exec.Command
然后代码中使用的是execCommand,这样做是便于我们下面进行Fake - 测试Fake的写法如下,可以和实际的测试用例不在同一个包中:
func fakeExecCommand(command string, args...string) *exec.Cmd { cs := []string{"-test.run=TestHelperProcess", "--", command} cs = append(cs, args...) cmd := exec.Command(os.Args[0], cs...) cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} return cmd } - 最终执行对象是一个测试用例,需要跟被测测试用例在一个包下面:
func TestHelperProcess(t *testing.T){ if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { return } cmdName = os.Args[3] // 0--是 go test 执行程序 1--是 TestHelperProcess 2-- 是“--” fmt.Fprintf(os.Stdout, "command name is %s\n ") os.Exit(0) } - 在用例中进行替换使用就可以了
execCommand = fakeExecCommand defer func(){ execCommand = exec.Command }()
这个方法相对优雅,而且可以进行针对不同命令进扩展,以及统计等。