(Translated by https://www.hiragana.jp/)
Goのtestifyが自分には大きすぎたので actually
2023/04/26

Goのtestifyが自分じぶんにはおおきすぎたので actually

仕事しごとの Go ではよくstretchr/testify利用りようしていますが、自分じぶんでライブラリをいてテストをこうとおもったとき、testify はちょっとでかすぎた。testifyはオールインワンテスティングフレームワークなのでそりゃそうさというところなんですが、それにしてもでかい。みんなそうおもわないのだろうか。

あと、有名ゆうめいな「Goはなぜアサーションをたないか」Why does Go not have assertions? という FAQ が存在そんざいするせいか、Goにはあまりアサーションライブラリがない。あるにはあるのだけど、結局けっきょく testify にしゅうれんしている。という印象いんしょう

NOTE: ここでいう testifyは、testify/assert のことです。その機能きのうについてはれません。

testifyのアサーションメソッド

というわけでまず、代表だいひょうてきなtestifyの2アサーションメソッドをあげてみます。

  • Equal
    • 2比較ひかくは reflect.DeepEqual もしくは bytes.Equal 相当そうとう
    • ポインタは中身なかみ(そのもの)をるだけでアドレス比較ひかくはしない
    • funcはれない
  • Exactly
    • Equal + かた一致いっちをみている(実状じつじょうとしては Equalかたちがうとちる)
  • Same
    • ポインタアドレスの一致いっちをみる
  • EqualValues
    • おも数値すうち比較ひかくようで、かた変換へんかん可能かのうおなじなら pass
  • IsType
    • "Is" とついているが2比較ひかくする
    • reflect.TypeOf で2かた比較ひかくする
  • EqualExportedValues
    • Struct比較ひかくよう
    • publicなフィールドと一致いっちしていれば pass

自分じぶんはこれらの機能きのう差分さぶんをいちいちおぼえていられない。使つかうたびにソースまでんだりしてる。みんなこれをおぼえているの?

これ以外いがいにも2アサーションではないけど特徴とくちょうてきなのをあげると Empty というメソッドもあり、いわゆる「そら」をみるのだけど、ちょっと抽象ちゅうしょうてきで、実際じっさいかたによってそれぞれ「そら」の意味いみがいろいろあってそれを判定はんていしていて、これもやっぱり自分じぶん毎回まいかいソースを参照さんしょうしてしまっている。PHPの empty名前なまえおなじだけど、ふわっとしすぎてるかんじもている。テストでふわっとする必要ひつようってあるんじゃろか。ちょう便利べんりだけど。

testifyのインターフェース

testifyは2とおりのしをサポートしている。

シンプルなほう

assert.Equal(t, expect, got, "they should be equal")

たくさんアサーションするときは New して t省略しょうりゃくすることができる。

assert := assert.New(t)
assert.Equal(expect, got, "they should be equal")

これはまあそういうもんですよね。

なんですけど、このアサーションの引数ひきすうで Expect/Got をわたすときの順番じゅんばん自分じぶんはめちゃくちゃになる。testifyの Expect/Got の順序じゅんじょは、Expectがさき(すべてではない!これもまぎらわしい)なのだけど、自分じぶんは Perl Test::More そだちなので Got をさききたくなる。また、実践じっせんてきに、テストをいているとき、Expectはほとんど確定かくていしていて、Gotをごにょごにょいて、いざアサーションとなると、またExpectをさきかなきゃいけなくなる。さっきまでGotについていてやっとれたGotを一度いちどわすれて Expectをかされる。自分じぶんはこれにすごく抵抗ていこうかんじる。Gotからけたほう便利べんりときだってあるはず。

このExpect/Gotの順番じゅんばん論争ろんそうというのはけっこういろんなところでされているらしいが、ちょっと調しらべてみたところ JUnit以降いこうは Expectがさきになっているらしい。

assertEqualsやassert_equalの引数ひきすうはなぜ expected, actual のじゅんなのか、調しらべてみた

まあ、自分じぶん調しらべてみたかんじ、たしかに Gotさきのものが圧倒的あっとうてきすくないなという結論けつろんだった。

でも、需要じゅようとしてはあるのですよ。らんけど。すくなくとも自分じぶんにはある。

(なお、expect/gotにたいして want/actual、もしくは expect/actual という表現ひょうげんべつもありますが、いったんここではそのちがいはれません)

Test Actually

というわけで、testify使つかってて便利べんりだけど自分じぶんはもっとになじむ小刀こがたなしい!testify V2ってられない! Don't ask. Just write. というおもいをむねきました。actually といいます。

2アサーションは以下いかのようなメソッドがあります

  • Same
    • testifyでいうところの Exactly
    • 2比較ひかくは reflect.DeepEqual もしくは bytes.Equal 相当そうとう
    • ポインタは中身なかみ(そのもの)をるだけでアドレス比較ひかくはしない
    • funcはれない
    • かた一致いっちまでみる
  • SameNumber
    • testifyのEqualValues
    • おも数値すうち比較ひかくようで、かた変換へんかん可能かのうおなじなら pass
    • かたにせず数値すうちをアサーションしたいときはこっち
  • SamePointer
    • testifyのSame
    • ポインタアドレスの一致いっちをみる

2アサーションはすべSame という接頭せっとうをつけていく方針ほうしん。エディタで補完ほかんされやすい。基本きほんてきSame使つかえばよい。Sameが strictすぎて実践じっせんてき使つかいづらいところだけべつメソッド(SameNumberSamePointer)で補完ほかんする計画けいかく

サンプルコードは以下いかのようなかんじです。

Go play ground

package main

import (
    "testing"
    "github.com/bayashi/actually"
)

func Test(t *testing.T) {
    love, err := getLove()
    actually.Got(love).True(t)
    actually.Got(err).Nil(t)
    actually.Got(love).Expect(true).Same(t)
    actually.Got(int32(1)).Expect(float64(1.0)).SameNumber(t)
    heart := &love
    body  := heart
    actually.Got(heart).Expect(body).SamePointer(t)
}

func getLove() (bool, error) {
    return true, nil
}

てのとおり、ビルダーパターンのようなインターフェースです。GotExpect のセッターが分離ぶんりしていて、どっちをさきいても大丈夫だいじょうぶです。もうこれで順番じゅんばんろんそうからはおさらばです。世界せかい明示めいじてきほういのです。むときもくときも、まよわないことのほう価値かちたかい!

それから、*testing.Tつねにアサーションメソッドにわたします。(これを一番いちばん最初さいしょわたすのもきじゃなかった。思考しこう途切とぎれる)

なお、actually ってタイプするのがながいよっておもひとみじかいエイリアス(a とか acc がおすすめ)をつけるか、禁断きんだんのドットimportするといいとおもいます。

また、Name("test name")明示めいじてき使つかうか、アサーションメソッドのだい引数ひきすうにテストめい設定せっていすることができます。

actually.Got(love).True(t, "We expect true love.")

Fail actually

さて、ここまで actually のテストコード本体ほんたいについてきましたが、テストライブラリにはテストコード以上いじょう大事だいじてんほかにもあります。

それは、こけたときのレポートです。

魚釣さかなつりはさかなれない時間じかん圧倒的あっとうてきながいので、れない時間じかんたのしめないとつづきません。

おなじように、開発かいはつしゃはこけたテストと時間じかん圧倒的あっとうてきです。こけたときのレポートがわかりやすく、修正しゅうせい方針ほうしんのヒントとならなければなりません。

そのてん、testifyはものすごくよくできています。(モバイルだと表示ひょうじくずれるのでPCでしい)

--- FAIL: Test (0.00s)
    testify_test.go:10:
                Error Trace:    /testify_test.go:10
                Error:          Not equal:
                                expected: 123
                                actual  : 1233
                Test:           Test
                Messages:       comp 123

このまとまってやすいレポートは、testifyをえら理由りゆうおおきなポイントではないかとおもいます。assertionについていろいろいたけど、testify の fail reportはものすごく便利べんりちょう便利べんり。testify最高さいこう

なので、わたしの actually は、testifyの出力しゅつりょくをしゅっとコピーしました。やったね! 節操せっそうつぎです。開発かいはつらくにしたいのです。

ただし、ただコピーするだけだとげいがないので、failレポートに必要ひつよう機能きのう独立どくりつさせて、そとから使つかうことも(一応いちおう)できるようにしています。もし、テストライブラリをおきのさいは、ご利用りようください。

  • report レポート生成せいせいよう
  • trace スタックトレース取得しゅとく
  • diff 2差分さぶん取得しゅとく

また、複雑ふくざつなテキスト同士どうし比較ひかくや、んだStructを比較ひかくしたときにこけた場合ばあい、"%#v" によるダンプや diff だけでなく、構造こうぞうそのもののdump全体ぜんたいたいときはありませんか? わたしはあります。

そんなとき便利べんりなメソッドも actually はそなえています。

actually.Got(a).Expect(b).X().Same(t)

上記じょうきのように X() というメソッドをんでおくと、こけたときに、以下いかのようなRawダンプを表示ひょうじしてくれます(以下いかれい文字もじれつなのでたままの表示ひょうじ。Structは spew によるダンプがます)。データが複雑ふくざつちがいが複数個ふくすうこしょある場合ばあいなど、きっとたすけになるとおもいます(ひろ表示ひょうじ領域りょういき必要ひつようになりますが)。手動しゅどうでダンプを手間てまはもう必要ひつようありません。

ちなみに、X は explain とおぼえてください。

builder_test.go:133:
            Trace:          /path/to/src/github.com/bayashi/goverview/builder_test.go:133
            Function:       TestTree()
            Fail reason:    Not same
            Expected:       Dump: "\n┌ 001/\n├── .gitignore\n├── LICENSE: License MIT\n├── go.mod: go 1.18\n└───+ main.go: main\n      Func: X\n      const: X\n"
            Actually got:   Dump: "\n┌ 001/\n├── .gitignore\n├── LICENSE: License MIT\n├── go.mod: go 1.19\n└──* main.go: main\n      Func: X\n      Const: X\n"
            Diff Details:   --- Expected
                            +++ Actually got
                            @@ -4,6 +4,6 @@
                             ├── LICENSE: License MIT
                            -├── go.mod: go 1.18
                            -└───+ main.go: main
                            +├── go.mod: go 1.19
                            +└──* main.go: main
                                   Func: X
                            -      const: X
                            +      Const: X
            Expected Raw:   ---
                            ┌ 001/
                            ├── .gitignore
                            ├── LICENSE: License MIT
                            ├── go.mod: go 1.18
                            └───+ main.go: main
                                  Func: X
                                  const: X
                            ---
            Got Raw:        ---
                            ┌ 001/
                            ├── .gitignore
                            ├── LICENSE: License MIT
                            ├── go.mod: go 1.19
                            └──* main.go: main
                                  Func: X
                                  Const: X
                            ---

やっぱり、百聞ひゃくぶん一見いっけんかずですよね。気持きもちいい。

というわけで、以上いじょうちいさなassertionライブラリ actually紹介しょうかいでした。

「Goはなぜアサーションをたないか」Why does Go not have assertions? にあるとおり、error handling と reporting を適切てきせつくことはなによりも重要じゅうようですが、人生じんせい有限ゆうげんですし、とくにチーム開発かいはつをする場合ばあい全員ぜんいん全員ぜんいん error handling と reporting を適切てきせつに "一貫いっかんせいのある" かたができるかというと、なかなかむずかしいのが現実げんじつだなあというのが自分じぶん印象いんしょうです。とくに、こけたときのレポーティングにかんしてはテストをくという本質ほんしつとははなれてくるがして、かく実装じっそうしゃ頑張がんば必要ひつよういというのが自分じぶん意見いけんです。なぜこのテストが存在そんざいするのか、なぜこの2比較ひかくなのかといった意義いぎや、Goのかたやゼロ、または充分じゅうぶんなカバレッジをたすテストケースぐんというのは、実装じっそうしゃ(開発かいはつチーム)の責務せきむだとおもいます。

actually はいまのところ v1 以前いぜんですし、おれおれによるおれのためのライブラリにしかなってないですけど、もしよかったらおためしください。また、ご意見いけん機能きのう要望ようぼうもおちしております。

ではでは。

サイトない検索けんさく