加入收藏 | 设为首页 | 会员中心 | 我要投稿 东莞站长网 (https://www.0769zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 资源网站 > 资源 > 正文

理解 Golang 子进程测试

发布时间:2020-02-15 02:04:25 所属栏目:资源 来源:掘金
导读:最近在写 logger 的 单元测试 的时候遇到了一个问题,如果直接执行 logger.Fatal,由于这个函数底层调用了 os.Exit(1) ,进程会直接终止,testing 包会认为 test failed.虽然这是个简单的函数,而且几乎用不上,但是迫于强迫症,必须给他安排一个单元测试。 后
  最近在写 logger 的单元测试的时候遇到了一个问题,如果直接执行 logger.Fatal,由于这个函数底层调用了 os.Exit(1) ,进程会直接终止,testing 包会认为 test failed.虽然这是个简单的函数,而且几乎用不上,但是迫于强迫症,必须给他安排一个单元测试。  后来意外找到了Andrew Gerrand(Golang的开发者之一) 在 Google I/O 2014 上一篇关于测试技巧的slide(见附录),里面有讲到 subporcess tests, 也就是子进程测试,内容如下:  Sometimes you need to test the behavior of a process, not just a function.  To test this code, we invoke the test binary itself as a subprocess:   这里讲到如果我们要测试进程的行为,而不仅仅是函数,那么我们可以通过单元测试的二进制文件创建一个子进程来测试。所以回过头来,从测试的角度出发,我们需要测试Fatal函数的这两个行为:  打印日志文本  有错误地终止进程  所以我们单元测试就需要覆盖函数的这两个行为,Andrew Gerrand 讲的子进程测试的技巧,正好适用这种情况。所以可以参考这个例子,给Fatal写一个单元测试  假设我们的Fatal函数是基于标准库的log包的封装   这里我们可以先看下标准库log包的实现(事实上 zap/logrus 等log包的Fatal函数也是类似的,最终都调用了os.Exit(1))  先按正常的思路写一个单元测试  执行单元测试的结果如下,如我之前所说,结果是FAIL   我们照猫画虎,尝试写一个子进程测试,这里我把标准输出和标准错误输出都打印出来了  执行单元测试,结果果然是成功的,达到了我们的预期  当然,我们不仅要知其然,更要知其所以然。我们分析一下子进程测试代码为什么是这样写  通过os.Getenv获取环境变量,这里值为空,所以Fatal并不会执行  定义了outb,errb,这里是为了后续捕捉标准输出和标准错误输出  调用exec.Command 根据传入的参数构造一个Cmd的结构体  exec 是标准库中专门用于执行命令的的包,这里不做太多赘述  我们可以看到,exec.Cmmand 第一个参数是要执行的命令或者二进制文件的名字,第二个参数是不定参数,是我们需要执行的命令的参数  这里我们第一个参数传入了os.Args[0],  os.Args[0]是程序启动时的程序的二进制文件的路径,第二个参数是执行二进制文件时的参数。至于为什么是os.Args[0]而不是os.Args[1]或者os.Args[2]呢,我们执行一下go test -n,你会看到输出了一堆东西(省略了大部分无关内容)  从输出的内容我们可以知道go test最终是将源码文件编译链接成二进制文件(当然还有govet静态检查)执行的。实际上go test和go run最终调用的是同一个函数,这篇文章对此也不做过多讨论,具体可以查看源码中cmd/go/internal/test/test.go和cmd/go/internal/work/build.go这两个文件的内容。  而-n 参数,可以打印go test或者go run 执行过程中用到的所有命令,所以我们在输出的最后一行,执行了最终的二进制文件并且带上了-test.timeout=10m0s默认超时的flag。而os.Args是os包的一个常量,在进程启动时,就会把执行的命令和flag写入   所以 os.Args[0]自然获取的就是编译后的二进制文件的完整文件名。  第二个参数-test.run=TestFatal 是执行二进制文件的flag,test.runflag 指定的test的函数名。  当我们执行go test -run TestFatal时,实际上最终就是执行成$WORK/b001/log.test -run=TestFatal  其他flag可以执行go help testflag查看,或者参考 cmd/go/internal/test/testflag.go文件中的testFlagDefn传入,具体定义和说明都在源码中。  cmd.Env 设置子进程运行的环境变量  os.Environ() 获取当前环境变量的拷贝,我们添加一个SUB_PROCESS环境变量用户判断是否是子进程。  cmd.Stdout = &outb  cmd.Stderr = &errb  捕获子进程运行时的标注输出和标准错误,因为我们需要测试是否输出  cmd.Run()  启动子进程,等待返回结果 如果退出,可能会返回exec.ExitError,可以拿到退出的statusCode,而我们的目的就是测试进程是否退出  在子进程中,此时环境变量SUB_PROCESS的值为1,这时候会执行Fatal函数,主进程收到exit code,打印子进程的输出  至此,这段测试代码的原理我们也清楚了。  但是,美中不足的是,在执行go test -cover进行测试覆盖率统计的时候,通过子进程运行的单元测试的函数,并不会被统计上。

(编辑:东莞站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读