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

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("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测试中使用接口做了很多研究。但到目前为止,我无法完全理解这一点。至少当我需要 "mock"(抱歉使用这个词)golang std 时,我看不到接口如何帮助我。像 "fmt".

这样的库包

我想到了两个场景:

  1. 使用os/exec测试命令行界面
  2. wrap fmt 包所以我可以控制并能够检查输出字符串

我不喜欢这两种情况:

  1. 我体验过实际的命令行是一个复杂且性能不佳的过程(见下文)。也可能有可移植性问题。
  2. 我相信这是要走的路,但我担心包装 fmt 包可能需要大量工作(至少包装 time 包以进行测试是一项非常重要的任务 (https://github.com/finklabs/ttime) ).

这里的实际问题:还有其他 (better/simpler/idiomatic) 方式吗? 注:我想在纯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")
    }
}

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

这可以在测试工具中被覆盖为新的(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!")
    }
}

我是不是漏掉了什么,还是你在说 testable examples

基本上,它是这样工作的:在 *_test.go 文件中,您需要遵守约定 Example[[T][_M]],其中 T 是类型的占位符,M 您想要在 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