DBへCRUD操作を行う処理のテストコードをどのように実装するか

DBに接続しているAPIやアプリケーションにおいて、INSERT文でレコードを作成したり、SELECT文でデータを取得する処理のテスト方法についてまとめます。

どのような方法があるか

CRUD操作を行う処理のテストコードの書き方は、下記の2つの方法があります。

  • テスト用のDBを用意し、実際にCRUD処理を行ったあとの結果を比較する方法。
  • モックを利用し、発行されるSQLと返り値を評価する方法。

テスト用のDBを用意し、実際にCRUD処理を行ったあとの結果を比較する方法。

この方法はシンプルでわかりやすいです。DBに接続して実際に処理を実行するので、本番環境に近い結果が得られます。しかし短所もあって、作成・更新・削除系の処理はデータベースのレコードが変わってしまうため、他のテストケースに影響がでないよう元の状態に復元するなどして、テストケースごとに独立性を保つ必要があります。サンプルデータを用意するのも結構たいへんです。テストケースが増えると処理が重くなりがちで、CIに時間がかかるようになります。また、意図的にエラーを発生させることができないケースが多く、異常系のテストが難しい or できないです。

テストケースごとの独立性を維持するための考え方ですが、私の経験した開発では下記の手法を採用しました。

  1. テスト用のデータベース作成(テストケースごとに作る。データベース名はuuidなどを付与して被らないようにする)
  2. サンプルデータを流し込む(必要に応じてマイグレーションも必要)
  3. テストが終わったらデータベースを削除

テストケースが増えるごとに上記の処理が行われるので、実装が増えるほどCIの時間が増え、最終的にCIだけで10min近くかかるようになりました。データベースを作成するのではなく、テストケースごとにロールバックして復元するなど、DBのリソースを使い回すような設計にすべきでした。

メリット

  • シンプルでわかりやすい
  • 本番環境に近い結果が得られる

デメリット

  • サンプルデータを用意する必要がある
  • 実際にDBへ接続するので処理が重くなりがち
  • テストケースごとの独立性を維持する方法を考えないといけない
  • ユニットテストではなくインテグレーションテストになってしまう
  • 異常系のテストが難しい

モックを利用し、発行されるSQLと返り値を評価する方法。

sqlmockなどのモックツールを使う方法は、テスト用のDBを用意しなくても良い点が大きな長所だと思います。しかし発行されたクエリが一致しているかの確認と関数の返り値の比較が主になるので、実際のDBで処理を実行したらうまくいかないケースがでてきそうです。そのため、DBへ実際に接続しての動作はE2Eテストやfeatureテストで担保する必要があります。モックの使い方を覚えないといけないので、学習コストも少しかかります。

メリット

  • テスト用のDBが不要
  • DBを用意する方法に比べて処理が軽い・速い

デメリット

  • 厳密なテストができない
  • モックツールの学習コスト

まとめ

現在私が担当しているプロダクトでは、当初は「テストDBを用意する方法」を採用していましたが、今後はユニットテストで「モックを使う方法」、E2Eテストで「テストDBを用意する方法」。という形で、両方のやり方を適材適所に使っていく方針です。