kitslog

何かを書く

Go 1.13beta1 のerrorsの挙動をテストする

こんにちは、@tkitsunaiです

Go 1.13beta1がリリースされていたので、errors周りについて色々挙動をテストしてみました

Go 1.13beta1のダウンロードはここからできます -> Downloads - The Go Programming Language

リリースノートはこちら -> Go 1.13 Release Notes - The Go Programming Language

この記事のコードを直接見る場合はGitHubへどうぞ

github.com

テストのAssertionにgithub.com/stretchr/testifyを利用しています

基本:新しくエラーを作る

err := errors.New("it is error")
assert.EqualError(t, err, "it is error")

結果

=== RUN   Testエラーを新しく作成する
--- PASS: Testエラーを新しく作成する (0.00s)

エラーをWrapする

err1 := errors.New("this is error 1")
wrapError := fmt.Errorf("wrap error: %w", err1)
assert.EqualError(t, wrapError, "wrap error: this is error 1")
assert.Equal(t, false, wrapError == err1)

結果

=== RUN   Testエラーをエラーで包み込む
--- PASS: Testエラーをエラーで包み込む (0.00s)

注意しなければならないのは、: %wでWrapするという点です

エラーの等値性をチェックをする

新機能のerrors.Is関数を使います。

err1 := errors.New("this is error 1")
assert.True(t, errors.Is(err1, err1))
assert.True(t, err1 == err1)

結果

=== RUN   Testエラーの値を同値かチェックする_生成された同じエラーを使う
--- PASS: Testエラーの値を同値かチェックする_生成された同じエラーを使う (0.00s)

同じインスタンスを使っているので、等値。Isを使わない場合の動作も従来通り等値。

別のインスタンスでIsを使う場合

err1 := errors.New("this is error 1")
err2 := errors.New("this is error 1")
assert.Equal(t, false, errors.Is(err1, err2))

結果

=== RUN   Testエラーの値を同値かチェックする_エラーのテキストは同じ
--- PASS: Testエラーの値を同値かチェックする_エラーのテキストは同じ (0.00s)

もちろんインスタンスが違うので、等値ではない

Wrapされたエラーに対してIsでチェックする

今回の目玉

err := errors.New("this is error")
wrappedError := fmt.Errorf("wrap error: %w", err)
assert.True(t, errors.Is(wrappedError, err))

結果

=== RUN   Test包まれた最初のエラーであることを同じかを判定する
--- PASS: Test包まれた最初のエラーであることを同じかを判定する (0.00s)

Isを使うことで、WrapされたwrappedErrorがerrであることが確認できました

良いですね。

では何回もWrapするとどうでしょうか

err := errors.New("this is error")
wrappedError1 := fmt.Errorf("one wrap: %w", err)
wrappedError2 := fmt.Errorf("two wrap: %w", wrappedError1)
assert.True(t, errors.Is(wrappedError2, err))

結果

=== RUN   Test包まれた最初のエラーであること同じかを判定する_何回も包む
--- PASS: Test包まれた最初のエラーであること同じかを判定する_何回も包む (0.00s)

これも、Isを使うと複数回包まれたwrappedError2がerrであることが確認できました

正しくWrapできなかった場合

Wrapするには、%w でフォーマットしてあげる必要がありました

%w以外でWrapしたつもりになったらどうでしょうか

err := errors.New("error")
wrappedError := fmt.Errorf("wrap error: %s", err)
assert.Equal(t, false, errors.Is(wrappedError, err))

例えば%sで包みます

結果

=== RUN   Testエラーを包む時にフォーマットしない場合は同一性が担保できない
--- PASS: Testエラーを包む時にフォーマットしない場合は同一性が担保できない (0.00s)

等値ではないことが確認できました

errors.Asを使って、エラー型を変換する

As関数も新機能です

独自エラー型を使う場合やエラーそのものを取り扱いたい場合にはAsが使えそうです

type NotFoundError struct {
    Err   string
    Cause string
}

func (n NotFoundError) Error() string {
    return fmt.Sprintf("%s: %v", n.Err, n.Cause)
}

func FindHoge() error {
    return &NotFoundError{
        Err:   "not found error",
        Cause: "i dont know",
    }
}

func Testエラーに対しAsを利用して指定したエラー型に変換する(t *testing.T) {
    if err := FindHoge(); err != nil {
        var notFoundErr *NotFoundError
        if errors.As(err, &notFoundErr) {
            assert.Equal(t, "i dont know", notFoundErr.Cause)
            return
        }
    }
    t.Fail()
}

FindHoge()を実行した結果、返ってきたerrorインターフェースがNotFoundErrorであるか。

もしNotFoundErrorに変換できていれば、bool値でtrueが返ってくる。

結果

=== RUN   Testエラーに対しAsを利用して指定したエラー型に変換する
--- PASS: Testエラーに対しAsを利用して指定したエラー型に変換する (0.00s)

期待通りです

では、変換できない場合

type UnknownError struct {
    Err string
}

func (s UnknownError) Error() string {
    return s.Err
}

func FindSomething() error {
    return &UnknownError{
        Err: "unknown error",
    }
}

func Testエラーに対しAsを利用して指定したエラー型に変換したけど変換できなかった(t *testing.T) {
    err := FindSomething()
    var notFoundErr *NotFoundError
    assert.Equal(t, false, errors.As(err, &notFoundErr))
    assert.Nil(t, notFoundErr)
}

FindHogeで返ってくるのはUnknownError型のため、NotFoundErrorに変換できません

結果

=== RUN   Testエラーに対しAsを利用して指定したエラー型に変換したけど変換できなかった
--- PASS: Testエラーに対しAsを利用して指定したエラー型に変換したけど変換できなかった (0.00s)

これも期待通りです。notFoundErrをポインタにしておかないと、zero value structとなるので注意が必要です

WrapされたエラーをAsで変換する

func TestWrapされたエラーに対しAsを利用して指定したエラー型に変換する(t *testing.T) {
    err := FindHoge()
    wrapError := fmt.Errorf("wrapping error: %w", err)
    var notFoundErr *NotFoundError
    if errors.As(wrapError, &notFoundErr) {
        assert.NotNil(t, notFoundErr)
        assert.EqualError(t, notFoundErr, "not found error: i dont know")
        return
    }
    t.Fail()
}

Wrapした場合、Asでちゃんと変換できるか?

結果

=== RUN   TestWrapされたエラーに対しAsを利用して指定したエラー型に変換する
--- PASS: TestWrapされたエラーに対しAsを利用して指定したエラー型に変換する (0.00s)

大丈夫ですね。

まとめ

全体的に問題なく使えるのではないでしょうか。正式リリースが楽しみです。

正式リリースはまだ先なのでそれまではxerrorsで代用しましょう。