【Golang】testingパッケージのError/ErrorfとFatal/Fatalfの違い

Golangのプログラムのテストを書く際、 testingパッケージ の関数 Error / ErrorfFatal / Fatalf の違いが分からなくなる時があるので整理してみました。

まとめの表

Error / Errorf / Fatal / Fatalf は簡易関数で、以下の

をそれぞれ異なる組み合わせで実行しています。

簡易
関数
実行される処理
Error Log: エラーログに引数で渡されたテキストを記録する。
Fail: 対象の関数のテストに失敗した記録を残すが、後続のテストは実行する
Errorf Logf: エラーログに引数で渡されたフォーマットでテキストを記録する。
Fail: 対象の関数のテストに失敗した記録を残すが、後続のテストは実行する
Fatal Log: エラーログに引数で渡されたテキストを記録する。
FailNow: 対象の関数のテストに失敗した記録を残し、後続のテストは実行しない
Fatalf Logf: エラーログに引数で渡されたフォーマットでテキストを記録する。
FailNow: 対象の関数のテストに失敗した記録を残し、後続のテストは実行しない

実装・挙動の違いから、上記の表のようになっていることを確認していきます。

実装

まず、実装の点から違いを確認します。

Go 1.16時点でのそれぞれの関数の実装は以下のようになっていて、ログ出力の処理とFailマークを付ける処理を行っていることが分かります。(補足のコメントを追記しています。)

func (c *common) Log(args ...interface{}) {
    c.log(fmt.Sprintln(args...))
}
func (c *common) Logf(format string, args ...interface{}) {
    c.log(fmt.Sprintf(format, args...))
}

func (c *common) Error(args ...interface{}) {
    c.log(fmt.Sprintln(args...)) // ↑のLog関数と同じ
    c.Fail()
}

func (c *common) Errorf(format string, args ...interface{}) {
    c.log(fmt.Sprintf(format, args...)) // ↑のLogf関数と同じ
    c.Fail()
}

func (c *common) Fatal(args ...interface{}) {
    c.log(fmt.Sprintln(args...)) // ↑のLog関数と同じ
    c.FailNow()
}

func (c *common) Fatalf(format string, args ...interface{}) {
    c.log(fmt.Sprintf(format, args...)) // ↑のLogf関数と同じ
    c.FailNow()
}

挙動

Error / ErrorfFatal / Fatalfそれぞれの挙動を確認してみます。

挙動の確認に使うコード

  • 足し算をするだけの関数 (calc.go)
    • error には nil を返す
  • 関数からエラーが返ってこないかを確認するテストコード(calc_test.go)

を使って確認してみます。

package calc

func calc(a, b int) (int, error) {
    return a + b, nil
}
package calc

import "testing"

func TestCalc1(t *testing.T) {
    ret, err := calc(1, 2)
    if err != nil {
        t.Error("[Error]", ret, err)
    }
    t.Log("[END]TestCalc1 with Error")
}

func TestCalc2(t *testing.T) {
    ret, err := calc(1, 2)
    if err != nil {
        t.Errorf("[Error] ret:%d, err: %v", ret, err)
    }
    t.Log("[END]TestCalc2 with Errorf")
}

func TestCalc3(t *testing.T) {
    ret, err := calc(1, 2)
    if err != nil {
        t.Fatal("[Fatal]", ret, err)
    }
    t.Log("[END]TestCalc3 with Fatal")
}

func TestCalc4(t *testing.T) {
    ret, err := calc(1, 2)
    if err != nil {
        t.Fatalf("[Fatal] ret:%d, err: %v", ret, err)
    }
    t.Log("[END]TestCalc4 with Fatalf")
}

calc 関数はエラーが返らないようにしてあるので、テストを実行すると以下のようにすべてパスします。

$ go test -v
=== RUN   TestCalc1
    calc_test.go:10: [END]TestCalc1 with Error
--- PASS: TestCalc1 (0.00s)
=== RUN   TestCalc2
    calc_test.go:18: [END]TestCalc2 with Errorf
--- PASS: TestCalc2 (0.00s)
=== RUN   TestCalc3
    calc_test.go:26: [END]TestCalc3 with Fatal
--- PASS: TestCalc3 (0.00s)
=== RUN   TestCalc4
    calc_test.go:34: [END]TestCalc4 with Fatalf
--- PASS: TestCalc4 (0.00s)
PASS
ok      go-testing  0.019s

テスト対象の関数 calc からエラーを返してみる

テスト対象の関数 calcを変更して必ずエラーが返るようにしてみます。

package calc

import (
        "errors"
)

func calc(a, b int) (int, error) {
    return a + b, errors.New("error in calc")
}

再度テストを実行すると、いずれのテストもFAILしていますが

  • Error/Errorf関数を使ったテストの場合、最後までテストを実行できる
    • テスト関数の最後のログ出力: [END]TestCalcX出力されている
  • Fatail/Fatalf関数を使ったテストの場合、途中でテストが終わっている
    • テスト関数の最後のログ出力: [END]TestCalcX出力されていない

という違いがあることが分かります。

$ go test -v
=== RUN   TestCalc1
    calc_test.go:8: [Error] 3 error in calc
    calc_test.go:10: [END]TestCalc1 with Error
--- FAIL: TestCalc1 (0.00s)
=== RUN   TestCalc2
    calc_test.go:16: [Error] ret:3, err: error in calc
    calc_test.go:18: [END]TestCalc2 with Errorf
--- FAIL: TestCalc2 (0.00s)
=== RUN   TestCalc3
    calc_test.go:24: [Fatal] 3 error in calc
--- FAIL: TestCalc3 (0.00s)
=== RUN   TestCalc4
    calc_test.go:32: [Fatal] ret:3, err: error in calc
--- FAIL: TestCalc4 (0.00s)
FAIL
exit status 1
FAIL    go-testing  0.040s

フォーマットを指定してログ出力した方が分かりやすいと感じます。

上記の例では、TestCalc1の中に [Error] 3 error in calcという内容になっていて、3回エラーが発生したのか、と勘違いしてしまいそうです。

TestCalc2 のようにフォーマットが指定されていれば、[Error] ret:3, err: error in calc のように返すことができ、ret は3が返ってきているけどエラーが発生したんだな、と分かるので、適宜フォーマットを指定してエラーメッセージを返してあげるのが良さそうです。

サンプルコード

github.com