单元测试在循环中使用致命流进行代码



我正在尝试将测试添加到 go cli 代码中。 代码有很多log.Fatal流。 谷歌搜索把我带到了这里,所以我遵循了它并让测试工作。 但是,我的测试方式是,它们被设置为使用不同参数在循环中运行正在测试的函数。

这是测试代码

func TestGetXXX_FatalFlow(t *testing.T) {
type args struct {
varA string
varB    string
}
tests := []struct {
name     string
args     args
expected string
}{
{
name:     "Scenario 1: varA and varB both blank",
args:     args{},
expected: "message1",
},
{
name: "Scenario 2: varA and varB not blank but invalid",
args: args{
varA: "somevalueA",
varB: "somevalueB",
},
expected: "message2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Only run the failing part when a specific env variable is set
if os.Getenv("BE_CRASHER") == "1" {
GetXXX(tt.args.serverName, tt.args.address)
return
}
// Start the actual test in a different subprocess
cmd := exec.Command(os.Args[0], "-test.run=TestGetXXX_FatalFlow")
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
stdout, _ := cmd.StderrPipe()
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
// Check that the log fatal message is what we expected
gotBytes, _ := ioutil.ReadAll(stdout)
if !strings.Contains(string(gotBytes), tt.expected) {
t.Fatalf("Unexpected log message. Got %s but should contain %s", strippedMsg, tt.expected)
}
// Check that the program exited
cmd.Env = append(os.Environ(), "BE_CRASHER=0")
err = cmd.Wait()
if e, ok := err.(*exec.ExitError); !ok || e.Success() {
t.Fatalf("Process ran with err %v, want exit status 1", err)
}
})
}
}

我遇到的问题是,我觉得我的方法 GetXXX 永远不会被第二对输入变量调用,不知何故,方法 GetXXX 一直只用测试数组中的第一对参数被调用。 我不太确定是否因为这会产生一个子进程。

任何帮助将不胜感激。 谢谢

如果你浏览代码,你会看到它正在做你所期望的:

  1. 外部测试运行开始,循环测试用例,未设置 env var,因此对于每次迭代,它都会分叉一个新的go test过程。
  2. 新进程开始运行相同的测试函数,该函数循环测试用例,设置了 env var,因此对于每次迭代,它都会调用GetXXX,这会崩溃。

您将在此处的第 2 步中看到,对于每次迭代,子进程都会运行第一个测试用例,该测试用例崩溃,并且永远不会到达第二个用例。父进程中的循环迭代无关紧要 - 它从不将测试用例的任何参数传递给子进程,因此子进程不知道父进程认为它正在测试哪个测试用例。它再次迭代案例本身,但只设法在崩溃之前执行第一个案例。

一般来说,我会建议不要使用这种结构(go test分叉一个新的go test(,我也建议不要在你的代码中的任何位置出现致命的。对于 99% 的情况,当出现问题时,您的函数应该返回一个error。对于真正无法恢复的致命错误,您应该使用panic,然后您可以测试它是否使用recoverlog.Fatal(我猜您正在使用的(只是打印一条日志消息,然后调用os.Exit,正如您所发现的那样,这使得几乎不可能进行测试。

如果正确构建程序真的不是一种选择,那么作为最后的手段,你可以做这样的事情(未经测试,但我希望它能明白这一点(:

crasher := os.Getenv("BE_CRASHER")
if crasher == "" {  // Parent process
for idx := range tests {
t.Run(tt.name, func(t *testing.T) {
// Start the actual test in a different subprocess
cmd := exec.Command(os.Args[0], "-test.run=TestGetXXX_FatalFlow")
cmd.Env = append(os.Environ(), fmt.Sprintf("BE_CRASHER=%d", idx))
stdout, _ := cmd.StderrPipe()
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
// Validate child process did as expected yadda yadda
})
}
} else {  // Child process
idx, err := strconv.Atoi(crasher)
if err != nil {
panic(err)
}
tt := tests[idx]
GetXXX(tt.args.serverName, tt.args.address)
return
}

这通过让父级迭代测试用例来更改它,当它分叉子用例时,它使用 env var告诉子级要运行哪个案例。然后,子项运行指定的事例。

最新更新