How to test a function's output (stdout/stderr) in unit tests

huangapple go评论85阅读模式
英文:

How to test a function's output (stdout/stderr) in unit tests

问题

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

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

但是我该如何测试这个函数实际发送到标准输出的内容呢?在Perl中,Test::Output可以实现我想要的功能。我知道我可以编写自己的样板代码来在Go中实现相同的功能(如这里所述):

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!")
}

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

英文:

I have a simple function I want to test:

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

But how can I test what the function actually sends to standard output? Test::Output does what I want in Perl. I know I could write all my own boilerplate to do the same in Go (as described 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!")
}

But that's a lot of extra work for every single test. I'm hoping there's a more standard way, or perhaps an abstraction library to handle this.

答案1

得分: 51

还有一件事要记住,没有什么能阻止你编写函数来避免样板代码。

例如,我有一个使用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

英文:

One thing to also remember, there's nothing stopping you from writing functions to avoid the boilerplate.

For example I have a command line app that uses log and I wrote this function:

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

Then used it like this:

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

Using this assert library: http://godoc.org/github.com/stretchr/testify/assert.

答案2

得分: 21

你可以选择三种方法之一。第一种方法是使用Examples

该包还可以运行和验证示例代码。示例函数可以包含一个以"Output:"开头的结尾行注释,与函数的标准输出进行比较。在运行测试时,会忽略前导和尾随空格。以下是一个示例的示例:

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

第二种方法(在我看来更合适)是为你的IO使用虚拟函数。在你的代码中,你可以这样做:

var myPrint = fmt.Print

func (t *Thing) print(min_verbosity int, message string) {
    if t.verbosity >= minv {
        myPrint(message) // 注意
    }
}

在你的测试中:

func init() {
    myPrint = fakePrint // fakePrint记录它应该打印的所有内容。
}

func Test...

第三种方法是在生产代码中使用fmt.Fprintf,并使用os.Stdout作为io.Writer,但在测试中使用bytes.Buffer

英文:

You can do one of three things. The first is to use Examples.

>The package also runs and verifies example code. Example functions may include a concluding line comment that begins with "Output:" and is compared with the standard output of the function when the tests are run. (The comparison ignores leading and trailing space.) These are examples of an example:

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

The second (and more appropriate, IMO) is to use fake functions for your IO. In your code you do:

var myPrint = fmt.Print

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

And in your tests:

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

func Test...

The third is to use fmt.Fprintf with an io.Writer that is os.Stdout in production code, but bytes.Buffer in tests.

答案3

得分: 0

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

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

现在,你的测试可以将返回的字符串与预期字符串进行比较(而不是打印输出)。这可能更符合测试驱动开发(TDD)的思路。

在你的生产代码中,不需要做任何更改,因为如果你不需要函数的返回值,就不必将其赋给变量。

英文:

You could consider adding a return statement to your function to return the string that is actually printed out.

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

Now, your test could just check the returned string against an expected string (rather than the print out). Maybe a bit more in-line with Test Driven Development (TDD).

And, in your production code, nothing would need to change, since you don't have to assign the return value of a function if you don't need it.

huangapple
  • 本文由 发表于 2014年11月7日 23:33:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/26804642.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定