英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论