英文:
how to deal with the "fmt" golang library package for CLI testing
问题
免责声明:祝您圣诞快乐,希望我的问题不会打扰您!
sample.go:
package main
import(
"fmt"
"os"
)
type sample struct {
value int64
}
func (s sample) useful() {
if s.value == 0 {
fmt.Println("错误:出现问题!")
os.Exit(1)
} else {
fmt.Println("愿原力与你同在!")
}
}
func main() {
s := sample{42}
s.useful()
s.value = 0
s.useful()
}
// 输出:
// 愿原力与你同在!
// 错误:出现问题!
// 退出状态 1
我对如何在golang测试中使用接口进行了大量研究。但到目前为止,我还没有完全理解这个概念。至少我无法看到接口在我需要“模拟”(抱歉使用这个词)golang标准库包如"fmt"时如何帮助我。
我提出了两种情况:
- 使用os/exec 来测试命令行界面
- 包装fmt 包,以便我能够控制并检查输出字符串
我不喜欢这两种情况:
- 我觉得通过实际命令行界面进行测试很复杂,而且性能不佳(请参见下文)。可能还会有可移植性问题。
- 我相信这是正确的方法,但我担心包装fmt包可能会很费力(至少包装time包进行测试被证明是一项非常复杂的任务(https://github.com/finklabs/ttime))。
实际问题是:是否有另一种(更好/更简单/更符合惯例)的方法?
注意:我想在纯粹的golang中完成这个,我对下一个测试框架不感兴趣。
cli_test.go:
package main
import(
"os/exec"
"testing"
)
func TestCli(t *testing.T) {
out, err := exec.Command("go run sample.go").Output()
if err != nil {
t.Fatal(err)
}
if string(out) != "愿原力与你同在!\n错误:这是错误的,没有用!\n退出状态 1" {
t.Fatal("命令行界面有问题")
}
}
英文:
Disclaimer: I wish you a merry XMas and I hope my question does not disturb you!
sample.go:
package main
import(
"fmt"
"os"
)
type sample struct {
value int64
}
func (s sample) useful() {
if s.value == 0 {
fmt.Println("Error: something is wrong!")
os.Exit(1)
} else {
fmt.Println("May the force be with you!")
}
}
func main() {
s := sample{42}
s.useful()
s.value = 0
s.useful()
}
// output:
// May the force be with you!
// Error: something is wrong!
// exit status 1
I did a lot of research on how to use interfaces in golang testing. But so far I was not able to wrap my head around this completely. At least I can not see how interfaces help me when I need to "mock" (apologies for using this word) golang std. library packages like "fmt".
I came up with two scenarios:
- use os/exec to test the command line interface
- wrap fmt package so I have control and am able to check the output strings
I do not like both scenarios:
- I experience going through the actual command line a convoluted and not-performant (see below). Might have portability issues, too.
- I believe this is the way to go but I fear that wrapping the fmt package might be a lot of work (at least wrapping the time package for testing turned out a non-trivial task (https://github.com/finklabs/ttime)).
Actual Question here: Is there another (better/simpler/idiomatic) way?
Note: I want to do this in pure golang, I am not interested in the next testing framework.
cli_test.go:
package main
import(
"os/exec"
"testing"
)
func TestCli(t *testing.T) {
out, err := exec.Command("go run sample.go").Output()
if err != nil {
t.Fatal(err)
}
if string(out) != "May the force be with you!\nError: this is broken and not useful!\nexit status 1" {
t.Fatal("There is something wrong with the CLI")
}
}
答案1
得分: 12
《Kerningham的书》第11章对这个问题给出了一个很好的解决方案。关键是将对fmt.Printline()
的调用更改为对fmt.Fprint(out, ...)
的调用,其中out
被初始化为os.Stdout
。
这可以在测试工具中被覆盖为new(bytes.Buffer)
,从而允许测试捕获输出。
请参阅https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo.go和https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo_test.go
编辑者:OP...
sample.go:
package main
import(
"fmt"
"os"
"io"
)
var out io.Writer = os.Stdout // 在测试期间进行修改
var exit func(code int) = os.Exit
type sample struct {
value int64
}
func (s sample) useful() {
if s.value == 0 {
fmt.Fprint(out, "Error: something is wrong!\n")
exit(1)
} else {
fmt.Fprint(out, "May the force be with you!\n")
}
}
func main() {
s := sample{42}
s.useful()
s.value = 0
s.useful()
}
// 输出:
// May the force be with you!
// Error: this is broken and not useful!
// exit status 1
cli_test.go:
package main
import(
"bytes"
"testing"
)
func TestUsefulPositive(t *testing.T) {
bak := out
out = new(bytes.Buffer)
defer func() { out = bak }()
s := sample{42}
s.useful()
if out.(*bytes.Buffer).String() != "May the force be with you!\n" {
t.Fatal("There is something wrong with the CLI")
}
}
func TestUsefulNegative(t *testing.T) {
bak := out
out = new(bytes.Buffer)
defer func() { out = bak }()
code := 0
osexit := exit
exit = func(c int) { code = c }
defer func() { exit = osexit }()
s := sample{0}
s.useful()
if out.(*bytes.Buffer).String() != "Error: something is wrong!\n" {
t.Fatal("There is something wrong with the CLI")
}
if code != 1 {
t.Fatal("Wrong exit code!")
}
}
英文:
Chapter 11 of Kerningham's Book gives a good solution to this question.
The trick is to change the calls to fmt.Printline() to calls to
fmt.Fprint(out, ...) where out is initialised to os.Stdout
This can be overwritten in the test harness to new(bytes.Buffer) allowing the
test to capture the output.
See https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo.go and
https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo_test.go
edited by OP...
sample.go:
package main
import(
"fmt"
"os"
"io"
)
var out io.Writer = os.Stdout // modified during testing
var exit func(code int) = os.Exit
type sample struct {
value int64
}
func (s sample) useful() {
if s.value == 0 {
fmt.Fprint(out, "Error: something is wrong!\n")
exit(1)
} else {
fmt.Fprint(out, "May the force be with you!\n")
}
}
func main() {
s := sample{42}
s.useful()
s.value = 0
s.useful()
}
// output:
// May the force be with you!
// Error: this is broken and not useful!
// exit status 1
cli_test.go:
package main
import(
"bytes"
"testing"
)
func TestUsefulPositive(t *testing.T) {
bak := out
out = new(bytes.Buffer)
defer func() { out = bak }()
s := sample{42}
s.useful()
if out.(*bytes.Buffer).String() != "May the force be with you!\n" {
t.Fatal("There is something wrong with the CLI")
}
}
func TestUsefulNegative(t *testing.T) {
bak := out
out = new(bytes.Buffer)
defer func() { out = bak }()
code := 0
osexit := exit
exit = func(c int) { code = c }
defer func() { exit = osexit }()
s := sample{0}
s.useful()
if out.(*bytes.Buffer).String() != "Error: something is wrong!\n" {
t.Fatal("There is something wrong with the CLI")
}
if code != 1 {
t.Fatal("Wrong exit code!")
}
}
答案2
得分: 2
我在这里是否漏掉了什么,还是你在谈论testable examples?
基本上,它的工作原理如下:在一个*_test.go
文件中,你需要遵循约定Example[[T][_M]]
,其中T
是类型的占位符,M
是你想要将可测试示例显示为示例代码的方法的占位符。如果函数只被称为Example()
,那么代码将显示为一个包示例。
在你的示例代码的最后一行下面,你可以放置一个像这样的注释:
// Output:
// Foo
现在,go test
将确保可测试示例函数完全输出// Output:
下面的所有内容(包括空格),否则它将使测试失败。
这里是一个实际的可测试示例的例子:
func ExampleMongoStore_Get() {
sessionId := "ExampleGetSession"
data, err := ms.Get(sessionId)
if err == sessionmw.ErrSessionNotFound {
fmt.Printf("Session ' %s ' not found\n", sessionId)
data = make(map[string]interface{})
data["foo"] = "bar"
ms.Save(sessionId, data)
}
loaded, _ := ms.Get(sessionId)
fmt.Printf("Loaded value ' %s ' for key ' %s ' in session ' %s '",
loaded["foo"],
"foo", sessionId)
// Output:
// Session 'ExampleGetSession' not found
// Loaded value 'bar' for key 'foo' in session 'ExampleGetSession'
}
编辑:在godoc.org上查看上述示例的输出。
英文:
Am I missing something here or are you talking of testable examples?
Basically, it works like this: In a *_test.go
file, you need to adhere to the convention Example[[T][_M]]
where T
is a placeholder for the type and M
a placeholder for the method you want to display the testable example as example code in the Godoc. If the function is just called Example()
, the code will be shown as a package example.
Below the last line of the code of your example, you can put a comment like this
// Output:
// Foo
Now go test
will make sure that the testable example function either exactly puts out everything below // Output:
(including whitespace) or it will make the test fail.
Here is an actual example for an testable example
func ExampleMongoStore_Get() {
sessionId := "ExampleGetSession"
data, err := ms.Get(sessionId)
if err == sessionmw.ErrSessionNotFound {
fmt.Printf("Session '%s' not found\n", sessionId)
data = make(map[string]interface{})
data["foo"] = "bar"
ms.Save(sessionId, data)
}
loaded, _ := ms.Get(sessionId)
fmt.Printf("Loaded value '%s' for key '%s' in session '%s'",
loaded["foo"],
"foo", sessionId)
// Output:
// Session 'ExampleGetSession' not found
// Loaded value 'bar' for key 'foo' in session 'ExampleGetSession'
}
Edit: Have a look at the output of above example at godoc.org
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论