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

Unit testing 在循环中使用致命流对go代码进行单元测试,unit-testing,go,Unit Testing,Go,我正在尝试向go cli代码添加测试。 代码有大量的log.Fatal流。 一点谷歌搜索吸引了我,所以我跟着它,让测试正常进行。 但是,按照我的测试方式,它们被设置为在具有不同参数的循环中运行正在测试的函数 这是测试代码 func TestGetXXX_FatalFlow(t *testing.T) { type args struct { varA string varB string } tests := []struct {

我正在尝试向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方法总是用tests数组中的第一对参数调用。 我不太确定这是否是因为它产生了一个子进程

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

如果您仔细阅读代码,您会看到它正在做您期望的事情:

  • 外部测试运行开始,在测试用例上循环,没有设置env-var,因此对于每个迭代,它会派生出一个新的
    go-test
    过程
  • 新流程开始运行相同的测试函数,在测试用例上循环,设置了env var,因此对于每次迭代,它调用
    GetXXX
    ,这会崩溃
  • 您将看到,在这里的第2步中,对于每个迭代,子进程都运行第一个测试用例,这会崩溃,而它永远不会进入第二个用例。父进程中的循环迭代是不相关的——它从不将测试用例的任何参数传递给子进程,因此子进程不知道父进程认为它正在测试哪个测试用例。它再次迭代案例本身,但在崩溃之前只执行第一个案例

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

    如果正确构建程序真的、真的不是一个选项,那么作为最后的手段,您可以做类似的事情(未经测试,但我希望它能让人明白这一点):


    这通过让父级迭代测试用例来改变它,当它派生子级时,它使用env var告诉子级要运行哪个用例。然后,孩子只运行指定的案例。

    我喜欢你的答案,我确实觉得需要重新考虑此代码,以避免
    log.fatal
    调用。谢谢
    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
    }