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

Testing 如何处理;fmt“;用于CLI测试的golang库包,testing,go,mocking,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

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

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
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
我做了很多关于如何在golang测试中使用接口的研究。但到目前为止,我还不能完全理解这一点。至少当我需要“mock”(抱歉使用这个词)golang标准库包(如“fmt”)时,我看不出接口如何帮助我

我想出了两个方案:

  • 使用os/exec测试命令行界面
  • 包装fmt包,这样我就可以控制并能够检查输出字符串
  • 我不喜欢这两种情况:

  • 我在实际的命令行中经历了一个复杂而没有性能的过程(见下文)。可能也有可移植性问题
  • 我相信这是一条路,但我担心包装fmt包可能需要很多工作(至少包装测试时间包是一项非常重要的任务())
  • 这里的实际问题是:还有其他(更好/更简单/惯用的)方法吗? 注意:我想在纯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")
        }
    }
    
    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!")
        }
    }
    

    克宁汉姆书的第11章很好地解决了这个问题。 诀窍是将对fmt.Printline()的调用更改为对 fmt.Fprint(out,…),其中out初始化为os.Stdout

    这可以在测试线束中覆盖为新的(bytes.Buffer),从而允许 测试以捕获输出

    看到和

    编辑:OP。。。 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
    
    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(
        "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")
        }
    }
    
    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!")
        }
    }
    

    我是不是错过了什么,还是你在说什么

    基本上,它是这样工作的:在
    *\u test.go
    文件中,您需要遵守约定
    示例[[T][\u M]]
    ,其中
    T
    是类型的占位符,
    M
    是要在Godoc中将可测试示例显示为示例代码的方法的占位符。如果只调用函数
    Example()
    ,则代码将显示为包示例

    在示例代码的最后一行下面,您可以放置如下注释

    // Output:
    // Foo
    
    现在
    go test
    将确保可测试示例函数准确地输出
    //输出:
    (包括空格),否则将导致测试失败

    下面是一个可测试示例的实际示例

    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'
    }
    

    编辑:看一看

    哇,我不知道派克写了一本书。请说明这本书的书名或参考书目好吗。我喜欢你的建议。我即将像那样重构我的代码,并向您报告。非常感谢你!Go编程语言Alan A.A.Donovan·Brian W.Kernighan于2015年10月26日出版平装本,11月20日出版电子书Addison Wesley;380pp;ISBN:978-0134190440我按照你的建议重构了代码。我希望你不介意我在你的回答中添加了我的想法,以使其明确。如果我误解了什么,请改变它。顺便说一下,os.Exit.Looks似乎有问题。正如您所说,os.Exit存在一个问题。很难对调用exit的函数进行单元测试,因为这将中止单元测试。最好是返回某种错误代码,这样就行了。但最好避免调用Exit,而是返回一个有用的错误(通常情况下为零)。看见