unit-testing - 循环中带有致命流程的单元测试代码

标签 unit-testing go

我正在尝试将测试添加到 go cli 代码中。
代码有很多log.Fatal流动。
有点谷歌搜索导致我here ,所以我跟着它进行了测试。
但是,按照我的测试方式,它们被设置为在具有不同参数的循环中运行正在测试的函数。

这是测试代码

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 方法总是被测试数组中的第一对参数调用。
我不太确定是不是因为这会产生一个子进程。

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

最佳答案

如果您浏览代码,您会发现它正在执行您期望的操作:

  • 外部测试运行开始,循环测试用例,未设置 env var,因此对于每次迭代,它都会产生一个新的 go test过程。
  • 新进程开始运行相同的测试函数,循环测试用例 , env var 已设置,因此对于每次迭代,它调用 GetXXX , 崩溃。

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

    一般来说,我会建议不要使用这种结构( go test fork 出一个新的 go test ),我也建议不要在代码中的任何地方出现 fatal error 。对于 99% 的情况,您的函数应该返回 error当出现问题时。对于真正无法恢复的 fatal error ,您应该使用 panic ,然后您可以使用 recover 对其进行测试. log.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
    }
    

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

    关于unit-testing - 循环中带有致命流程的单元测试代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59569009/

    相关文章:

    javascript - 如何让 karma 将 webpack 的模式设置为开发模式?

    单元测试的 Python 覆盖率

    python - 仅使用 pycharm 的 Django 测试错误 - 不是终端 |应用程序尚未加载

    java - 如何使用whenNew方法模拟ArrayList类

    go - fatal error : 'libavcodec/avcodec.h' file not found caused by zergon321/reisen

    go - 如何使用反射来检查结构字段的类型是否为接口(interface){}?

    json - 使用 simplejson 转 JSON

    android - 如何设置 Mockito 模拟 Android 单元测试类

    go - 尝试运行 docker-compose up -d 时出现错误

    go - 如何在 go 中修复并发合并排序