Golangのプログラムのテストを書く際、 testingパッケージ の関数 Error
/ Errorf
と Fatal
/ 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
/ Errorf
と Fatal
/ 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が返ってきているけどエラーが発生したんだな、と分かるので、適宜フォーマットを指定してエラーメッセージを返してあげるのが良さそうです。