Repositoryのユニットテストの苦しみから解放されたい

こんにちは、BEエンジニアのミックです。

突然ですが、みなさんテストは好きですか?

ユニットテストの仕方はどうしてますか?

この記事では、テストコードを書くにあたって私が苦しかったことと、それを解決するためのアイディアと、導入結果について雑多に書いていこうと思っています。

私の苦しみ

私の入社当初から、弊社ではRepositoryのテストにはsqlmockを利用していました。

このsqlmockはDBを立ち上げなくてもReposioryのクエリのテストはできる大変便利なものではあるものの、使っていく中でいくつか課題が出てきました。

1.複雑なクエリではMockに合わせるのが苦しい

まず、実際のクエリの生成にORMを利用してる環境では、sqlmockに渡す期待値のクエリは、ORMが生成したクエリと同じものを設定する必要がありますが、ORMが生成するために、細かなフォーマット自体は実際に吐き出されるタイミングでないとわからないというのがあります。

次に、Mockの結果と期待値が違う場合に、sqlmockが吐き出すテスト結果の視認性の問題から、長文クエリだとどこが期待通りでないのか?吐き出される長文クエリからパッと確認することができないということです。

そのため、テスト結果をコピーして別途diffでの確認が必要になるような事態になり、最終的にはあまりに時間がかかりすぎるので、Mockが吐き出した実際値のクエリをコピーして期待値に貼り付けるという状況もチームでは起きていました。

2.クエリ自体が意図したものを取得できるかはテストできない

テストにMockを使っているため、実際クエリの動作が意図通りかは、ユニットテストレベルではできず、E2Eテストまで上げないと確認することができないという点も課題でした。これは、E2Eテストレベルで確認するテストケースの量がクエリの複雑さに従いどんどん増大していくということで、テストピラミッド的にはアンチパターンのアイスクリームコーンになる原因の一つでもありました。

これらのことから、Repositoryのユニットテスト自体が、ただ開発フローを守るためだけにテストを通すだけの作業になってしまい、テストの意義から考えればほぼテストが形骸化して死んでしまったというところがあります。

最終的には、Reposioryのテストをする事自体のコストが大きいために、決済まわりなど時に入念に確認したいというところ以外はあまり使われなくなり、全体的なテストのコスト面からある程度は意図的にそうしたというのはあれど、多くのユニットテストで確認できるテストケースをE2Eレベルのテストでカバーすることになりました。

私は幸せになりたい

E2Eテストがユニットテストより肥大する状況には不慣れだったので個人的にはとにかくこの状況は苦しく、状況を打破するために、 テストで確認したい事を改めて見つめてみると、結局のところ、求めてることは「Repositoryを使って期待通りのEntitiyを取得できるか?」という点に尽きると思っており、その観点から適切なテストを考えると、テスト用のDBコンテナと接続してブラックボックスでテストした方が良さそうだという事で試験的に導入してみる事にしました。

試している途中結果として

主に新規開発の機能でのテストの一部で試していますが、E2Eテストでは拾い切れないレベルの詳細なテストをユニットテストレベルで確認する事ができるようになったので、テストコードを書くコストや、テストにかける時間と精神的苦痛は、以前より大きく減りました。

ユニットテストが増えた分、E2Eテストの量はへり、本来E2Eレベルで確認すべきケースに以前より注力できるようになりましたし、考慮すべきケースが減った事で組み合わせテストでの認知的負荷も大分軽減されたと感じてます。

終わりに

依然としてテスト周りの課題は色々あるものの、結果としては、私は以前より幸せになりました。最終的に見積り時点でテストの工数の方が実装工数より多いという状況になる事が起こり得ないようテストを管理できるようになっていると私はもっと幸せになれそうなので、さらに幸せを追求していきたいと思います。

また、例えばRepositoryが吐き出すクエリが引数に依存して可変の場合など、明示的に生成されるクエリを確認するためにホワイトボックスでテストするべきところもあると思うので、用途に合わせてどんなテストをするべきか選択できると良いのではないかと思います。