如何测试包含log.Fatal()的Go函数

and*_*ing 21 testing go

说,我有以下代码打印一些日志消息.我如何测试是否记录了正确的消息?作为log.Fatal调用os.Exit(1),测试失败.

package main

import (
    "log"
)

func hello() {
    log.Print("Hello!")
}

func goodbye() {
    log.Fatal("Goodbye!")
}

func init() {
    log.SetFlags(0)
}

func main() {
    hello()
    goodbye()
}
Run Code Online (Sandbox Code Playgroud)

以下是假设性测试:

package main

import (
    "bytes"
    "log"
    "testing"
)


func TestHello(t *testing.T) {
    var buf bytes.Buffer
    log.SetOutput(&buf)

    hello()

    wantMsg := "Hello!\n"
    msg := buf.String()
    if msg != wantMsg {
        t.Errorf("%#v, wanted %#v", msg, wantMsg)
    }
}

func TestGoodby(t *testing.T) {
    var buf bytes.Buffer
    log.SetOutput(&buf)

    goodbye()

    wantMsg := "Goodbye!\n"
    msg := buf.String()
    if msg != wantMsg {
        t.Errorf("%#v, wanted %#v", msg, wantMsg)
    }
}
Run Code Online (Sandbox Code Playgroud)

Von*_*onC 11

这类似于" 如何os.Exit()在Go中测试场景 ":你需要实现自己的记录器,默认情况下重定向到log.xxx(),但是在测试时给你机会替换log.Fatalf()你自己的函数(不调用os.Exit(1))

我在测试os.Exit()呼叫方面做了同样的事情exit/exit.go:

exiter = New(func(int) {})
exiter.Exit(3)
So(exiter.Status(), ShouldEqual, 3)
Run Code Online (Sandbox Code Playgroud)

(这里,我的"退出"功能是空的,什么都不做)


Poh*_*How 10

如果您正在使用logrus,现在有一个选项可以定义此提交中引入的v1.3.0 中的退出函数。所以你的测试可能看起来像:

func Test_X(t *testing.T) {
    cases := []struct{
        param string
        expectFatal bool
    }{
        {
            param: "valid",
            expectFatal: false,
        },
        {
            param: "invalid",
            expectFatal: true,
        },
    }

    defer func() { log.StandardLogger().ExitFunc = nil }()
    var fatal bool
    log.StandardLogger().ExitFunc = func(int){ fatal = true }

    for _, c := range cases {
        fatal = false
        X(c.param)
        assert.Equal(t, c.expectFatal, fatal)
    }
}
Run Code Online (Sandbox Code Playgroud)


cls*_*ung 8

我使用以下代码来测试我的功能.在xxx.go中:

var logFatalf = log.Fatalf

if err != nil {
    logFatalf("failed to init launcher, err:%v", err)
}
Run Code Online (Sandbox Code Playgroud)

在xxx_test.go中:

// TestFatal is used to do tests which are supposed to be fatal
func TestFatal(t *testing.T) {
    origLogFatalf := logFatalf

    // After this test, replace the original fatal function
    defer func() { logFatalf = origLogFatalf } ()

    errors := []string{}
    logFatalf = func(format string, args ...interface{}) {
        if len(args) > 0 {
            errors = append(errors, fmt.Sprintf(format, args))
        } else {
            errors = append(errors, format)
        }
    }
    if len(errors) != 1 {
        t.Errorf("excepted one error, actual %v", len(errors))
    }
}
Run Code Online (Sandbox Code Playgroud)


All*_*uce 7

我会使用非常方便的bouk/monkey包(这里还有stretchr/testify)。

func TestGoodby(t *testing.T) {
  wantMsg := "Goodbye!"

  fakeLogFatal := func(msg ...interface{}) {
    assert.Equal(t, wantMsg, msg[0])
    panic("log.Fatal called")
  }
  patch := monkey.Patch(log.Fatal, fakeLogFatal)
  defer patch.Unpatch()
  assert.PanicsWithValue(t, "log.Fatal called", goodbye, "log.Fatal was not called")
}
Run Code Online (Sandbox Code Playgroud)

我建议在走这条路线之前阅读使用 bouk/monkey注意事项


vou*_*rus 6

虽然可以测试包含log.Fatal的代码,但不建议这样做.特别是,您无法以-cover标志所支持的方式测试该代码go test.

相反,建议您更改代码以返回错误,而不是调用log.Fatal.在顺序函数中,您可以添加额外的返回值,并且在goroutine中,您可以在类型的通道chan error(或包含类型错误的字段的某种结构类型)上传递错误.

一旦进行了更改,您的代码将更容易阅读,更容易测试,并且它将更加可移植(现在除了命令行工具之外,您还可以在服务器程序中使用它).

如果您有log.Println电话我还建议将自定义记录器作为字段传递给接收器.这样您就可以登录到自定义记录器,您可以将其设置为服务器的stderr或stdout,以及用于测试的noop记录器(因此您不会在测试中获得大量不必要的输出).该log软件包支持自定义记录器,因此无需编写自己的记录器或导入第三方软件包.