【问题标题】:How to test os.exit scenarios in Go如何在 Go 中测试 os.exit 场景
【发布时间】:2014-12-01 06:15:24
【问题描述】:

鉴于此代码

func doomed() {
  os.Exit(1)
}

如何正确测试调用此函数是否会导致使用go test 退出?这需要在一组测试中进行,换句话说,os.Exit() 调用不会影响其他测试,应该被捕获。

【问题讨论】:

  • 当然这不是问题的直接答案,这就是为什么我不把它写成一个整体,但一般来说:避免编写这样的代码。如果你只是Exit“在世界的尽头”(main),like this pattern,那么你就不会像这里的(好)公认的解决方案那样编写痛苦的测试。我完全承认您可能一直在测试别人的代码,而您无法轻易重构,但只是希望这些建议对未来的读者有所帮助……
  • 如果您确实遵循该模式并且碰巧使用了 Gomega,那么它有 a pretty cool gexec package 非常适合以黑盒方式测试可执行文件的结果。

标签: testing go exit-code


【解决方案1】:

你不能,你必须使用exec.Command 并测试返回值。

【讨论】:

    【解决方案2】:

    如果不从外部(使用exec.Command)过程模拟测试,我认为您无法测试实际的os.Exit

    也就是说,您可以通过创建接口或函数类型然后在测试中使用 noop 实现来实现您的目标:

    Go Playground

    package main
    
    import "os"
    import "fmt"
    
    type exiter func (code int)
    
    func main() {
        doExit(func(code int){})
        fmt.Println("got here")
        doExit(func(code int){ os.Exit(code)})
    }
    
    func doExit(exit exiter) {
        exit(1)
    }
    

    【讨论】:

    • 能否也添加测试?
    【解决方案3】:

    测试代码:

    package main
    import "os"
    
    var my_private_exit_function func(code int) = os.Exit
    
    func main() {
        MyAbstractFunctionAndExit(1)
    }
    
    func MyAbstractFunctionAndExit(exit int) {
        my_private_exit_function(exit)
    }
    

    测试代码:

    package main
    
    import (
        "os"
        "testing"
    )
    
    func TestMyAbstractFunctionAndExit(t *testing.T) {
        var ok bool = false // The default value can be omitted :)
    
        // Prepare testing
        my_private_exit_function = func(c int) {
            ok = true
        }
        // Run function
        MyAbstractFunctionAndExit(1)
        // Check
        if ok == false {
            t.Errorf("Error in AbstractFunction()")
        }
        // Restore if need
        my_private_exit_function = os.Exit
    }
    

    【讨论】:

    • 为了更清楚,这个答案建议将os.Exit 放在一个变量中,并在生产代码中通过这个变量调用它,而在测试中你可以用另一个函数替换它的值'不退出,但如果它被调用,会通知你。如果您有能力修改生产代码以提高其可测试性,这是一个有效的解决方案。
    【解决方案4】:

    Andrew Gerrand(Go 团队的核心成员之一)在 presentation 中展示了如何做到这一点。

    给定一个函数(main.go

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func Crasher() {
        fmt.Println("Going down in flames!")
        os.Exit(1)
    }
    

    这是您将如何测试它(通过main_test.go):

    package main
    
    import (
        "os"
        "os/exec"
        "testing"
    )
    
    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)
    }
    

    代码所做的是通过exec.Command 在单独的进程中再次调用go test,将执行限制为TestCrasher 测试(通过-test.run=TestCrasher 开关)。它还通过第二次调用检查的环境变量 (BE_CRASHER=1) 传入一个标志,如果设置,则调用被测系统,然后立即返回以防止陷入无限循环。因此,我们被退回到原来的调用站点,现在可以验证实际的退出代码。

    来源:Slide 23 Andrew 的演示文稿。第二张幻灯片也包含指向presentation's video 的链接。 他在47:09

    谈论子流程测试

    【讨论】:

    • 运行测试结果在:process ran with err exec: "cmd": executable file not found in $PATH, want exit status 1
    • @Alfred 您是否将实现和测试分别保存在单独的文件中,例如 main.gomain_test.go?我修改了答案并仔细检查了它是否可以在我的机器上运行。
    • 是的。他们是分开的。是否有可能是某些环境变量go env 有问题?我总是使用go test 进行测试,并且我还创建了几个测试来测试其他文件。
    • 这种方法不能让-cover显示你的行已经测试过
    • 对于需要重新执行以进入挂载命名空间的测试的代码覆盖率,我提出了一种解决方案,该解决方案会自动将重新执行的单独代码覆盖率合并到最终覆盖率中;请参阅github.com/thediveo/gons 以及这里的 gons/reexec 和 gons/reexec/testing 包。 reexec 代码面向命名空间重新进入,但您将很快了解如何在不支持命名空间的情况下使用配置文件合并来制作自己的 re-exec。
    【解决方案5】:

    我使用bouk/monkey

    func TestDoomed(t *testing.T) {
      fakeExit := func(int) {
        panic("os.Exit called")      
      }
      patch := monkey.Patch(os.Exit, fakeExit)
      defer patch.Unpatch()
      assert.PanicsWithValue(t, "os.Exit called", doomed, "os.Exit was not called")
    }
    

    monkey 在处理此类工作、故障注入和其他困难任务时非常强大。它确实来了with some caveats

    【讨论】:

    • 供参考,bouk/monkey的license可能和你的项目不兼容:github.com/bouk/monkey/pull/18
    • 您不应该使用任何软件,除非您对所有风险(包括法律风险)感到满意。你也不应该假设你可以通过阅读来理解软件许可证,这个或任何其他的。
    • 确实,考虑到许可证,没有人应该使用它。在这种情况下,理解许可证非常简单明了。即使代码可用,许可证也不允许使用它。在您的个人私人项目中,没有实际意义,但对于其他任何事情都很重要。
    • 该许可证在此人居住的欧盟无效。另一方面,我不会以任何形式或形式使用它,并改变我的程序的工作方式。除了他正在做的事情令人难以置信的肮脏之外,我会觉得依赖一个显然没有以理性或专业方式行事的开发人员制作的项目感到不舒服,这只是一个等待发生的左垫情况。 reuters.com/article/…
    • 但是,如果你无论如何都会感到恐慌,并且你喜欢脏代码,你可以这样做,我被告知我不应该写,但我喜欢它:p 我不会建议它,或者如果您喜欢它,请复制代码,因为我没有以稳定的方式维护它,并且一直在更改。另一种选择,可能更好的是使用 docker 开发人员开发的 re-exec 技巧,因为这实际上是在实际项目中使用的维护代码。我会同意的... github.com/TheApeMachine/errnie/blob/master/guard.go?ts=4
    猜你喜欢
    • 1970-01-01
    • 2019-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-10
    • 1970-01-01
    相关资源
    最近更新 更多