Goのアサーションライブラリ actually のすべて
ここ 1ヶ月くらい書いている Go のアサーションライブラリ actually ですが、ぼちぼち pull-request を重ねて、そろそろ v1.0.0 も見えてきたのではないかということで Release を v0.13.0 で作りました。
いまのところ、圧倒的な 俺の俺による俺のためのテスティングライブラリと化しています が、そろそろ他の人にも使ってみてもらいたい気持ちが湧いてきています。というわけで、ここでいったん現状のメソッドすべてに解説を書いてみようと思います。
actuallyの概要
インターフェースは以下の様な感じです。
actually.Got(v).True(t)
actually.Got(v).Expect(vv).Same(t)
1値をアサーションする場合も、Got()
でテストする値をセットして、各アサーションメソッドを *testing.T
を引数にして呼び出します。
2値をアサーションする場合は、Got()
と Expect()
で値をセットします。Got()
と Expect()
はどっちを先に書いても大丈夫です。また、2値アサーションメソッドは、すべて Same
という接頭辞ではじまります。インターフェースは、読むとき、書くとき、同じように一貫して明示的であることを重要視しています。
テストが こけたときのフェイルレポート は以下の様な感じです。
=== RUN Test
prog_test.go:10:
Trace: /tmp/sandbox1598591906/prog_test.go:10
Function: Test()
Fail reason: Not same value
Expected: Type: int, Dump: 2
Actually got: Type: int, Dump: 1
--- FAIL: Test (0.00s)
FAIL
開発中、テストはさまざまな理由でこけます。なぜこけたのか、どうあるべきなのか、受け取った値はどんな型で具体的に中身は何だったのか、フェイルレポートが理解しやすいことはとても重要です。actually のフェイルレポートのフォーマットは testify をコピーしていますが、actually では「なぜこけたのか」という具体的な理由がより細かく表示されるようになっています。
というわけで、アサーションメソッドをひとつひとつ、全部紹介していきます!
1値アサーション
まずは、ひとつの値をアサーションするメソッドたちを紹介します。
True
actually.Got(v).True(t)
v
が boolean型の true であればパスします。boolean型でなかったり、true でなければこけます。
False
actually.Got(v).False(t)
v
が boolean型の false であればパスします。boolean型でなかったり、false でなければこけます。
Nil
actually.Got(v).Nil(t)
v
が nil
であればパスします。
NotNil
actually.Got(v).NotNil(t)
v
が nil
以外であればパスします。
NoError
actually.Got(err).NoError(t)
err
が nil
であればパスします。
NoError
メソッドのための、特別な Got
が存在します。
actually.GotError(err).NoError(t)
GotError
は Got
と同じ actual value のセッターですが、error型以外を受け付けません。より厳密に扱えます。
2値アサーション
つづいて、2値アサーションです。2値アサーションメソッドは、すべて Same
という接頭辞ではじまります。
Same
actually.Got(v).Expect(vv).Same(t)
v
と vv
が同じ値であればパスします。型が違うとこけます。値が同じであれば、ポインタアドレスは違っていてもパスします。
2値が同一であるかをみるには、ほとんどがこの Same
を使うことになると思います。testifyで言うところの Equal
です(厳密には Exactly
と同じです)。
SameNumber
actually.Got(v).Expect(vv).SameNumber(t)
型が違っても、v
と vv
が convertable で同じ値とみなすことができればパスします。int32 と int64 や、int と float などの型違いの2値を比較したいとき用です。
SamePointer
actually.Got(ptr1).Expect(ptr2).SamePointer(t)
ptr1
と ptr2
が同じアドレスを指していればパスします。
SameType
actually.Got(v).Expect(vv).SameType(t)
v
の型が vv
の型と一致していればパスします。値は関係ありません。型の一致だけを見ます。
便利メソッド
actually には開発とテストを助けるいくつかのメソッドが準備されています。
Name
actually.Got(v).Name("v is true").True(t)
テストに名前をつけることができます。
この Name
は、明示的なメソッドとして用意されていますが、実際はアサーションメソッドの第二引数にテストの名前を書くことができます。
actually.Got(v).True(t, "v is true")
X
actually.Got(v).X().True(t)
X
はテストがこけたときに値を生データとして出力します。複数のタブや改行を含む込み入った文字列のテストのデバッグに役立ちます。
Diff
fmt.Println(Diff(a, b))
Diff
は、2つのオブジェクトの差分を diff で返します。例えばフェイルレポートでは Got と Expect の差分は表示されますが、開発中はもっと手前の Got のもとになる値と Got の値の差分を見たかったりもします。そうしたとき、両方の値をダンプして目で差分をみるよりも、機械的に Diff
で差分を出力することができると便利です。もちろん、文字列以外に構造体でも diff が見れます。
testing のラッパー
testing
モジュールの機能をラップしたメソッドもあります。
FailNow
actually.Got(v).FailNow().True(t)
FailNow
を呼ぶと、そのテストがこけると、そこでテスト全体を終了させます。有効範囲はそのテストのみです。
FailNowOn
actually.FailNowOn(t)
FailNowOn(t)
を呼ぶと、そのテストファンクションの間、FailNow が有効になります。
FaiNotNow / FailNotNowOn
FaiNotNow / FailNotNowOn は、それぞれ FailNow と FailNowOn の逆で、テストがこけても後続のテストを実行するスイッチです。デフォルトの挙動です。FailNotNow は呼んだテストでのみ有効で、FailNotNowOn はテストファンクションの間ずっと有効です(テストがこけても後続のテストを続けて実行するのはデフォルトの挙動なので、特に指定がなければ(FailNowOn が呼ばれていなければ、明示的に呼ばれなくても FailNotNow が有効です))。
なお、個別のテストで呼ぶフラグの方が、ファンクション全体のスイッチよりも優先されます。つまり、FailNowOn が呼ばれたあとに、個別のテストで FailNotNow が呼ばれると、そのテストでは FailNotNow となり、こけても後続のテストが実行されます。
コードで見る方がわかりやすいかもしれません。
func Test(t *testing.T) {
actually.FailNowOn(t)
actually.Got(something).Nil(t) // Fail Now
actually.Got(something).Expect(something).Same(t) // Fail Now
actually.FailNotNowOn(t)
actually.Got(something).Nil(t) // NOT Fail Now
actually.Got(something).Expect(something).Same(t) // NOT Fail Now
actually.Got(something).FailNow().Nil(t) // Fail Now
}
Skip
actually.Skip(t)
Skip
は単純に テスト実行時の -short
オプションを見てテストをスキップするためのスイッチのショートカットです。
以上です。
ちなみに、errorのアサーションメソッドは今のところ NoError
だけで、肝心の error そのものをアサーションするメソッドは実装していません。イケてる感じのを思いついたらやろうと思っているのですが、なかなか思いつかないので保留してます。だれか error のアサーションに一過言あるひといたらこっそり教えて欲しいです。
ではでは。