英文:
Unable to test a Golang CLI tool's output
问题
我有一个用Go编写的命令行工具,产生以下输出:
Command: config get
Env: int
Component: foo-component
无法在Cosmos(http://api.foo.com)中找到foo-component的任何配置。
我想在测试中验证这个输出。
我编写的测试(未通过)如下:
package command
import (
"testing"
"github.com/my/package/foo"
)
type FakeCliContext struct{}
func (s FakeCliContext) String(name string) string {
return "foobar"
}
func ExampleInvalidComponentReturnsError() {
fakeBaseURL := "http://api.foo.com"
fakeCliContext := &FakeCliContext{}
fakeFetchFlag := func(foo.CliContext) (map[string]string, error) {
return map[string]string{
"env": "int",
"component": "foo-component",
}, nil
}
GetConfig(*fakeCliContext, fakeFetchFlag, fakeBaseURL)
// Output:
// Command: config get
// Env: int
// Component: foo-component
//
// Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
}
大部分代码是创建我注入到函数调用GetConfig
中的虚假对象。
实际上,GetConfig
没有返回值,只是将文本打印到标准输出。
所以我使用Example<测试名称>
的格式来尝试验证输出。
但是当我运行go test -v
时,我只得到以下结果:
=== RUN ExampleInvalidComponentReturnsError
exit status 1
FAIL github.com/my/package/thing 0.418s
有人知道我可能漏掉了什么吗?
我发现,如果我在上面的'Example'之后添加一个额外的测试,例如命名为Test<测试名称>
(但实际上代码基本相同),那么当运行测试时,这将实际上将函数的输出显示到标准输出:
func TestInvalidComponentReturnsError(t *testing.T) {
fakeBaseURL := "http://api.foo.com"
fakeCliContext := &FakeCliContext{}
fakeFetchFlag := func(utils.CliContext) (map[string]string, error) {
return map[string]string{
"env": "int",
"component": "foo-component",
}, nil
}
GetConfig(*fakeCliContext, fakeFetchFlag, fakeBaseURL)
}
上面的示例测试现在在执行go test -v
时将显示以下输出:
=== RUN TestInvalidComponentReturnsError
Command: config get
Env: int
Component: foo-component
无法在Cosmos(http://api.foo.com)中找到foo-component的任何配置。
exit status 1
FAIL github.com/bbc/apollo/command 0.938s
英文:
I have a cli tool written in Go which produces the following output:
Command: config get
Env: int
Component: foo-component
Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
I would like to verify this output within a test.
The test I have written (and doesn't pass) is as follows:
package command
import (
"testing"
"github.com/my/package/foo"
)
type FakeCliContext struct{}
func (s FakeCliContext) String(name string) string {
return "foobar"
}
func ExampleInvalidComponentReturnsError() {
fakeBaseURL := "http://api.foo.com"
fakeCliContext := &FakeCliContext{}
fakeFetchFlag := func(foo.CliContext) (map[string]string, error) {
return map[string]string{
"env": "int",
"component": "foo-component",
}, nil
}
GetConfig(*fakeCliContext, fakeFetchFlag, fakeBaseURL)
// Output:
// Command: config get
// Env: int
// Component: foo-component
//
// Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
}
The majority of the code is creating fake objects that I'm injecting into my function call GetConfig
.
Effectively there is no return value from GetConfig
only a side effect of text being printed to stdout.
So I'm using the Example<NameOfTest>
format to try and verify the output.
But all I just back when I run go test -v
is:
=== RUN ExampleInvalidComponentReturnsError
exit status 1
FAIL github.com/my/package/thing 0.418s
Does anyone know what I might be missing?
I've found that if I add an additional test after the 'Example' one above, for example called Test<NameOfTest>
(but consistenting of effectively the same code), then this will actually display the function's output into my stdout when running the test:
func TestInvalidComponentReturnsError(t *testing.T) {
fakeBaseURL := "http://api.foo.com"
fakeCliContext := &FakeCliContext{}
fakeFetchFlag := func(utils.CliContext) (map[string]string, error) {
return map[string]string{
"env": "int",
"component": "foo-component",
}, nil
}
GetConfig(*fakeCliContext, fakeFetchFlag, fakeBaseURL)
}
The above example test will now show the following output when executing go test -v
:
=== RUN TestInvalidComponentReturnsError
Command: config get
Env: int
Component: foo-component
Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
exit status 1
FAIL github.com/bbc/apollo/command 0.938s
答案1
得分: 0
好的,这个问题的解决方案涉及到架构的一部分和代码的移除/重构。
-
我将 cli 命令包中的私有函数提取出来,使它们成为一个单独函数中的公共函数。
-
我重构了代码,将所有的依赖项注入,这样我就可以模拟这些对象并验证是否调用了预期的方法。
-
现在私有函数在一个包中并且被公开,我可以专门测试这些内容,而不受 cli 上下文的限制。
-
最后,我移除了对 os.Exit 的使用,因为那是一场噩梦,而且实际上并不是必要的。
英文:
OK so the solution to this problem was part architecture and part removal/refactor of code.
-
I extracted the private functions from the cli command package so they became public functions in a separate function
-
I refactored the code so that all dependencies were injected, this then allowed me to mock these objects and verify the the expected methods were called
-
Now the private functions are in a package and made public, I'm able to test those things specifically, outside of the cli context
-
Finally, I removed the use of os.Exit as that was a nightmare to deal with and wasn't really necessary
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论