【问题标题】:How to test a function's output (stdout/stderr) in unit tests如何在单元测试中测试函数的输出(stdout/stderr)
【发布时间】:2014-11-07 15:33:28
【问题描述】:

我想测试一个简单的函数:

func (t *Thing) print(min_verbosity int, message string) {
    if t.verbosity >= minv {
        fmt.Print(message)
    }
}

但是我怎样才能测试函数实际发送到标准输出的内容呢? Test::Output 在 Perl 中做我想做的事。我知道我可以编写自己的所有样板在 Go 中做同样的事情(如 here 所述):

orig = os.Stdout
r,w,_ = os.Pipe()
thing.print("Some message")
var buf bytes.Buffer
io.Copy(&buf, r)
w.Close()
os.Stdout = orig
if(buf.String() != "Some message") {
    t.Error("Failure!")
}

但是对于每一个测试来说,这都是很多额外的工作。我希望有一种更标准的方法,或者可能是一个抽象库来处理这个问题。

【问题讨论】:

    标签: testing go stdout


    【解决方案1】:

    还有一点要记住,没有什么能阻止你编写函数以避免样板。

    例如,我有一个使用log 的命令行应用程序,我编写了这个函数:

    func captureOutput(f func()) string {
        var buf bytes.Buffer
        log.SetOutput(&buf)
        f()
        log.SetOutput(os.Stderr)
        return buf.String()
    }
    

    然后这样使用:

    output := captureOutput(func() {
        client.RemoveCertificate("www.example.com")
    })
    assert.Equal(t, "removed certificate www.example.com\n", output)
    

    使用这个断言库:http://godoc.org/github.com/stretchr/testify/assert

    【讨论】:

    • 这可能是我要做的,因为它比@Ainar-G 提供的解决方案更通用,它要求我控制所有可能输出某些东西的代码。
    • 仅供参考,这不是常规安全的。当第一个 go-routing 仍在记录时,第二个 go-routine 可以将日志重置回 os.Stderr
    • 仅供参考:这只会捕获log 输出,而不是fmt 输出。
    • 为了测试具有日志输出的并发操作,这可能会导致写入缓冲区和读取缓冲区之间的竞争。如需解决方案,请在此处查看我的答案:stackoverflow.com/a/68807754/7655137
    【解决方案2】:

    你可以做三件事之一。第一种是使用Examples

    该包还运行并验证示例代码。示例函数可能包括以“输出:”开头的结束行注释,并在运行测试时与函数的标准输出进行比较。 (比较忽略前导和尾随空格。)这些是示例示例:

    func ExampleHello() {
            fmt.Println("hello")
            // Output: hello
    }
    

    第二个(更合适的是 IMO)是为您的 IO 使用假函数。在你的代码中你这样做:

    var myPrint = fmt.Print
    
    func (t *Thing) print(min_verbosity int, message string) {
        if t.verbosity >= minv {
            myPrint(message) // N.B.
        }
    }
    

    在你的测试中:

    func init() {
        myPrint = fakePrint // fakePrint records everything it's supposed to print.
    }
    
    func Test...
    

    第三个是在生产代码中使用fmt.Fprintfio.Writer,即os.Stdout,但在测试中使用bytes.Buffer

    【讨论】:

    • 示例适用于具有确定性输出的情况,而 fmt.Fprint 适用于您可以控制打印代码的情况(如果它在外部库或其他东西中,则无法控制)。我认为大多数时候这些事情之一将是真实的。如果有一种情况两者都不正确,那么将写入 os.Stdout 的内容读入字符串的选项会很好。
    • 我实际上使用了选项 2 和 3 的组合。首先我制作了一个打印机接口,它在我的代码库中使用。这使测试我的代码变得容易,并让我完全控制屏幕上的内容。其次,我测试我的打印机,它可以使用选项 3 将各种复杂类型的输出打印到 stdout 和 stderr。对于我的打印机的单元测试,我只需给它两个 bytes.Buffer 编写器而不是 os.Stdoutos.StdErr
    【解决方案3】:

    您可以考虑在您的函数中添加一个 return 语句来返回实际打印出来的字符串。

    func (t *Thing) print(min_verbosity int, message string) string {
        if t.verbosity >= minv {
            fmt.Print(message)
            return message
        }
        return ""
    }
    

    现在,您的测试可以只检查返回的字符串与预期的字符串(而不是打印输出)。也许更符合测试驱动开发 (TDD)。

    而且,在您的生产代码中,无需更改任何内容,因为如果您不需要函数的返回值,您不必指定它。

    【讨论】:

      猜你喜欢
      • 2020-10-18
      • 2011-02-06
      • 2017-04-18
      • 2013-05-06
      • 2019-04-21
      • 1970-01-01
      • 2014-09-13
      • 2017-08-26
      • 1970-01-01
      相关资源
      最近更新 更多