一度はうまくいっている。

何なら何度もテストを流して成功しているのに、忘れた頃にこっそり失敗している。

失敗したテストコードを確認してみても、テスト対象のソースでは擬似乱数は使っていないので毎回結果が変わるわけがないのだが。。??

Go言語を触りたての頃にハマった謎現象、最近久しぶりに相見えて謎でもなんでもなかったんだなあと思ったので解決までを残しておきます。

再現

テストしたいソースが以下だとします。
引数mapを構造体Setのkey,valueに詰め替えてSliceにする簡単なお仕事。

テストコードは一旦こう。
ポイントは期待値の比較をまるっとreflect.DeepEqualで行なっているところ。

これでテストを実行。

成功した! と思いきや何回か試しているうちに失敗パターンがきました。
期待値と実際の[]Setの要素の順番が違うようです。

なぜ失敗する?

まず、reflect.DeepEqualはsliceを比較する時、要素の並びまで完全に一致していないと等しいと判断してくれません。
公式:https://pkg.go.dev/reflect#DeepEqual

DeepEqualがfalseを返す理由はわかりました。

ではなぜ実行毎にsliceの並びが変わるのでしょうか?

答えはmapの仕様にありました。
mapは反復処理(range)で回す場合、その順番は指定されていないので毎回同じであることが保証されていません。
公式:https://go.dev/blog/maps

つまり、rangeによってmapから取り出されるkey,valueのセットは毎回違う順番で取り出され、sliceに詰められていたから期待値の順番と齟齬が出ていたのですね。

解決策

上記に貼っている公式ブログで、実装側でmapを個別にソートして固定の順番にする例が載っているので、
ここではテスト側を修正して実装側に影響が出ない方法を。

とは言いつつすることは単純で、
期待値と実際のsetを手書きでチェックするだけです。

期待値基準でチェックしているので、想定外のsetがある場合に備えて最後に長さチェックも忘れずに。

これで何回テスト実行しても失敗することがなくなりました。

さいごに