2023/12/05

今夜あなたは目撃者!テストノ治安ヲ再生セヨ!

この記事は、"Go 言語Advent Calendar 2023 シリーズ 2" 5日目になります。


こんばんは!はじめて Go のアドベントカレンダーにエントリーしました。@bayashi と申します。はじめてなのでタイトルにインパクトが欲しくて気合い入れたんですが、特にアイデアが浮かばず、みのもんたさんインスパイアになってしまいました。わからない人はそのままスルーして読み進めてもらって大丈夫です。深い意味はありません。

開発者はテストの失敗を目撃する!

さて、みなさんの中には業務で Go を書いている人も多いかと思います。私もそうです。なんらかのアプリケーションを開発して、もちろんテストも書いていると思います。業務での開発となると、チームで取り組むことが多いのではないでしょうか。チームでコードを書くと、その一貫性について問題がでやすくなります。本体コードでもそうですが、テストにおいてもしかりです。そして私が今回 注目したのが、テストが失敗したときの出力についてです。

特に Go においてはテーブルドリブンテストが推奨されていることもあり、条件が複雑になりがちで、fail したときの出力内容が充分ではなく、何が起きたのかさっぱりわかりにくいということが稀によくあります。え、ありませんか? ありますよね? あるでしょう?

もちろん、テーブルドリブンでなくてもこけて困ることは多々あります。テストの失敗の情報は典型的に不足しがちなのです。

そうした問題は、アサーションライブラリを利用していると一定解決されます。テストの実行からこけたときの出力までおおむね面倒を見てもらえるからです。しかし、Goにおいてはアサーションライブラリが推奨されていません。Goの他に覚えることが増えない方がいいとか、エラーやそのレポートは自力でハンドリングしなきゃだめよ、というおぼしめしがあるからです。

その点について、私は異論ありません。

テストの治安

しかし、テストがこけてこまる現実はけっこう深刻です。こけたら直さなければなりません。チーム開発においては、もういない誰かが書いたテストを見ることも多いでしょう。そしておおむね複雑でわかりにくい部分の奥の奥がこけるのです。そもそも、こけたときのレポートは疎かになりがちだと書きました。オリジナルでテストを書いた人以外はこけたところを見ることが少ないので、こけたときの状態に問題があってもレビューですり抜けやすいのです。カバレッジの計測はありますが、テストがこけたときのレポートが十分かどうかは機械的に判断するのが難しいのです。あるときテストがこけて、そのときはじめてレポートが十分でないことに気づいて空を見上げることになるのです。あーやれやれ。

テストがこけたとき、このテストは何者で、検査した値は何だったのか、typeは? 2値比較なら差分は?生データでは?Dumpは?修正するために何をすべきなのか? いつかのこける時に備えて、そういうのを逐一毎回丁寧に書くのは骨が折れます。パスしている間は必要がないけれど、ひとたびこけると著しく存在を求められるのです。テストがこけたときのレポートに安定して必要なものが含まれる、そういう状態をつくるためのものがあれば、テストの治安は向上するんじゃないか?

test fail なんて怖くない

そこで書いてみたのが、Witness (目撃者)というヘルパーです。

以下のように使います。簡単です。

package main

import (
    "testing"

    w "github.com/bayashi/witness"
)

func TestExample(t *testing.T) {
    g := "a\nb\nc"
    e := "a\nd\nc"

    if g != e {
        w.Got(g).Expect(e).Fail(t, "Not same")
    }
}

あまり説明する必要もないと思いますが、以下の一行で、テスト対象の2値を設定して、Fail メソッドにこける理由を渡しつつ呼び出します。

w.Got(g).Expect(e).Fail(t, "Not same")

ターミナルでの出力は以下のようになります。

Trace:          /home/usr/go/src/github.com/bayashi/witness/witness_test.go:14
Fail reason:    Not same
Type:           Expect:string, Got:string
Expected:       "a\nd\nc"
Actually got:   "a\nb\nc"

GotExpectany で何でも受け付けて、レポート出力する際によしなに見やすい、比較しやすい形式にします。

さらに、もっと細かい違いの確認をしたいときは、 ShowMore() というメソッドを呼んでおくと、

w.Got(g).Expect(e).ShowMore().Fail(t, "Not same")

以下のように diff や 生データでの表示が増えます。

Trace:          /home/usr/go/src/github.com/bayashi/witness/witness_test.go:14
Fail reason:    Not same
Type:           Expect:string, Got:string
Expected:       "a\nd\nc"
Actually got:   "a\nb\nc"
Diff details:   --- Expected
                +++ Actually got
                @@ -1,3 +1,3 @@
                 a
                -d
                +b
                 c
Raw Expect:     ---
                a
                d
                c
                ---
Raw Got:        ---
                a
                b
                c
                ---

上記の例はテストしている値が文字列ですが、その他の複雑な型や構造体の場合は、spew.Sdump した結果を表示する感じになります。

その他 詳細なメソッドはこちら をご覧ください。テスト名やオリジナルの項目を追加することもできるようになっています。

inspired by testify

さて、この fail レポートですが、どこかでみたことありますよね?

そうです。元の挙動は testify から拝借していて、過去に 自分のアサーションライブラリ を書いたときにまとめたのを、今回もう一押し整理整頓して、fail reportライブラリとして切り出しました。

まだひと通り書いてそこそこ動きました、という状態なのですが、よろしければお試しください。こけたときの記述や考える量が圧倒的に減って楽にれると思います。また、今はターミナルに出力して人間が読むことを想定した出力をしますが、今後はGithub Actionsのアノテーションにも対応したいなあと思っています。

ぜひ、ご意見ご感想など頂けるとうれしいです。

今夜あなたは目撃者!テストノ治安ヲ再生セヨ! Witness

おわり。

サイト内検索