无法测试 Golang 命令行工具的输出。

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

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 (
	&quot;testing&quot;

	&quot;github.com/my/package/foo&quot;
)

type FakeCliContext struct{}

func (s FakeCliContext) String(name string) string {
	return &quot;foobar&quot;
}

func ExampleInvalidComponentReturnsError() {
	fakeBaseURL := &quot;http://api.foo.com&quot;
	fakeCliContext := &amp;FakeCliContext{}
	fakeFetchFlag := func(foo.CliContext) (map[string]string, error) {
		return map[string]string{
			&quot;env&quot;:       &quot;int&quot;,
			&quot;component&quot;: &quot;foo-component&quot;,
		}, 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&lt;NameOfTest&gt; 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&lt;NameOfTest&gt; (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 := &quot;http://api.foo.com&quot;
	fakeCliContext := &amp;FakeCliContext{}
	fakeFetchFlag := func(utils.CliContext) (map[string]string, error) {
		return map[string]string{
			&quot;env&quot;:       &quot;int&quot;,
			&quot;component&quot;: &quot;foo-component&quot;,
		}, 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

好的,这个问题的解决方案涉及到架构的一部分和代码的移除/重构。

  1. 我将 cli 命令包中的私有函数提取出来,使它们成为一个单独函数中的公共函数。

  2. 我重构了代码,将所有的依赖项注入,这样我就可以模拟这些对象并验证是否调用了预期的方法。

  3. 现在私有函数在一个包中并且被公开,我可以专门测试这些内容,而不受 cli 上下文的限制。

  4. 最后,我移除了对 os.Exit 的使用,因为那是一场噩梦,而且实际上并不是必要的。

英文:

OK so the solution to this problem was part architecture and part removal/refactor of code.

  1. I extracted the private functions from the cli command package so they became public functions in a separate function

  2. 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

  3. Now the private functions are in a package and made public, I'm able to test those things specifically, outside of the cli context

  4. Finally, I removed the use of os.Exit as that was a nightmare to deal with and wasn't really necessary

huangapple
  • 本文由 发表于 2015年12月5日 03:11:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/34095768.html
匿名

发表评论

匿名网友

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

确定