如何处理用于CLI测试的“fmt”golang库包

时间:2015-12-25 11:13:02

标签: testing go mocking

免责声明:祝你圣诞快乐,我希望我的问题不会打扰你!

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

我做了很多关于如何在golang测试中使用接口的研究。但到目前为止,我还是无法彻底解决这个问题。至少我无法看到界面如何帮助我,当我需要“模拟”(道歉使用这个词)golang std。库包如“fmt”。

我提出了两种情况:

  1. 使用os / exec 来测试命令行界面
  2. 包装fmt 包所以我有控制权并且能够检查输出字符串
  3. 我不喜欢这两种情况:

    1. 我经历了实际的命令行,这是一个错综复杂但不具备性能的人(见下文)。可能还有可移植性问题。
    2. 我相信这是要走的路,但我担心包装fmt包可能需要做很多工作(至少包装测试的时间包变成了一个非常重要的任务(https://github.com/finklabs/ttime))。
    3. 这里的实际问题:还有另一种(更好/更简单/惯用)方式吗? 注意:我想在纯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) != "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")
          }
      }
      

2 个答案:

答案 0 :(得分:9)

Kerningham's Book的第11章为这个问题提供了一个很好的解决方案。 诀窍是将对fmt.Printline()的调用更改为调用  fmt.Fprint(out,...)其中out初始化为os.Stdout

这可以在测试工具中覆盖到new(bytes.Buffer)允许 测试以捕获输出。

https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo.gohttps://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 // 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!")
    }
}

答案 1 :(得分:2)

我在这里遗漏了什么,或者你在谈论testable examples

基本上,它的工作原理如下:在*_test.go文件中,您需要遵守惯例Example[[T][_M]],其中T是该类型的占位符,M a您想要将可测试示例显示为Godoc中的示例代码的方法的占位符。如果该函数只是调用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'
}

编辑:查看the output of above example at godoc.org