logger.Fatalos.Exit(1)

Sometimes you need to test the behavior of a process, not just a function.

   func Crasher() {
        fmt.Println("Going down in flames!")
        os.Exit(1)
    }
复制代码

To test this code, we invoke the test binary itself as a subprocess:

func TestCrasher(t *testing.T) {
    if os.Getenv("BE_CRASHER") == "1" {
        Crasher()
        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() {
        return
    }
    t.Fatalf("process ran with err %v, want exit status 1", err)
}
复制代码
Fatal
  • 打印日志文本
  • 有错误地终止进程
Fatal

假设我们的Fatal函数是基于标准库的log包的封装

    package logger

    func Fatal(v ...interface{}){
        log.Fatal(v...)
    }
复制代码
Fatalos.Exit(1)
func Fatal(v ...interface{}) {
	std.Output(2, fmt.Sprint(v...))  //输出到标准输出/标准错误输出
	os.Exit(1) // 有错误地退出进程
}
复制代码

先按正常的思路写一个单元测试

func TestFatal(t *testing.T) {
    Fatal("fatal log")
}
复制代码

执行单元测试的结果如下,如我之前所说,结果是FAIL

go test -v
=== RUN   TestFatal
2020/01/11 11:39:24 fatal log
exit status 1
FAIL    github.com/YouEclipse/mytest/log        0.001
复制代码

我们照猫画虎,尝试写一个子进程测试,这里我把标准输出和标准错误输出都打印出来了

func TestFatal(t *testing.T) {
	if os.Getenv("SUB_PROCESS") == "1" {
		Fatal("fatal log")
		return
	}
	var outb, errb bytes.Buffer
	cmd := exec.Command(os.Args[0], "-test.run=TestFatal")
	cmd.Env = append(os.Environ(), "SUB_PROCESS=1")
	cmd.Stdout = &outb
	cmd.Stderr = &errb
	err := cmd.Run()
	if e, ok := err.(*exec.ExitError); ok && !e.Success() {
		fmt.Print(cmd.Stderr)
		fmt.Print(cmd.Stdout)
		return
	}
	t.Fatalf("process ran with err %v, want exit status 1", err)
}

复制代码

执行单元测试,结果果然是成功的,达到了我们的预期

go test -v
=== RUN   TestFatal
2020/01/11 11:40:38 fatal log
--- PASS: TestFatal (0.00s)
PASS
ok      github.com/YouEclipse/mytest/log        0.002s
复制代码

当然,我们不仅要知其然,更要知其所以然。我们分析一下子进程测试代码为什么是这样写

os.Getenvoutberrbexec.Command
os.Args[0]os.Args[0]os.Args[0]os.Args[1]os.Args[2]go test -n
mkdir -p $WORK/b001/

#
# internal/cpu
#
... 
/usr/local/go/pkg/tool/linux_amd64/compile -o ./_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid 5WmoKx2_LnkcztVfW1Bj/5WmoKx2_LnkcztVfW1Bj -dwarf=false -goversion go1.13.5 -D "" -importcfg ./importcfg -pack -c=4 ./_testmain.go
...

/usr/local/go/pkg/tool/linux_amd64/link -o $WORK/b001/log.test -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=o8I_q2gkkk-Xda8yeh2G/5WmoKx2_LnkcztVfW1Bj/5WmoKx2_LnkcztVfW1Bj/o8I_q2gkkk-Xda8yeh2G -extld=gcc $WORK/b001/_pkg_.a
...
cd /home/yoyo/go/src/github.com/YouEclipse/mytest/log
TERM='dumb' /usr/local/go/pkg/tool/linux_amd64/vet -atomic -bool -buildtags -errorsas -nilfunc -printf $WORK/b052/vet.cfg
$WORK/b001/log.test -test.timeout=10m0s
复制代码
go testgo testgo runcmd/go/internal/test/test.gocmd/go/internal/work/build.go
-ngo testgo run-test.timeout=10m0s
// Args hold the command-line arguments, starting with the program name.
var Args []string
复制代码
os.Args[0]
-test.run=TestFataltest.rungo test -run TestFatal$WORK/b001/log.test -run=TestFatalgo help testflagcmd/go/internal/test/testflag.gotestFlagDefn
SUB_PROCESSexec.ExitErrorSUB_PROCESS1Fatal

至此,这段测试代码的原理我们也清楚了。

go test -cover

附录