こんにちは、メルカリでソフトウェアエンジニアをしている@yuki.watanabeと申します。以前のキャリアでは経理をしておりましたが心機一転し、現在はAccounting Productsチームにて社内の会計システムを開発しています。
この会計システムにおけるインテグレーションテストにおいて、従来はBigQueryへのクエリのロジックにモックが使われていましたが、BigQueryのエミュレート環境を用意して実際にクエリを実行する改善を行ないました。本記事ではこの改善の背景や工夫した点について紹介します。
会計システムの概要
我々が開発している会計システムは、メルカリやメルペイなどのMicroservicesから会計データを受け取り、保存し、月次で仕訳用の会計レポートを作成しています。一般的な会計システムの財務会計や管理会計の機能を全て備えているわけではなく、あくまでも仕訳をするために特化したシステムです。
会計レポートの作成機能は、主に以下のコンポーネントで構成されています。
- BigQuery: 会計データを保持するデータウェアハウスであり、会計帳簿としての役割を担います。
- Google SpreadSheet: 月次で勘定科目ごとに集計した会計データが出力されます。
- Kubernetesクラスタ上のPod: CronJobにより月次で定期実行し、会計レポートを作成します。このPod上のアプリケーションはGoで実装され、BigQueryを参照し、勘定科目ごとに集計した会計データをGoogle SpreadSheetへ出力しています。
会計システムに関しては、下記の記事の「Microservices化に合わせた新しい会計システム」という項でも詳しく説明されています。
Microservices と会計システム
会計レポート作成機能の課題
この会計レポートは経理や事業サイドの要望により作成しており、事業環境の変化に伴う出力様式の変更や新規のレポート作成への要望に応えていかねばなりません。
以前出力様式を変更しようとしたときのことです。BigQueryへのクエリを変更する必要がありました。はじめにインテグレーションテストを追加・修正し、期待する様式をテストコードに反映したうえでアプリケーション側のソースコードを修正しようと考えました。
しかし、テストコードにおいてBigQueryへのクエリが返す値にモックが使われていました。おおよそ、下記のイメージです。
mockdb.
EXPECT().
Rows(gomock.Any()).
Return(
[][]string{
{"item-sold", "2023-02", "1000", "accounts-receivable", "sales"},
{"item-shipping", "2023-02", "300", "accounts payable", "shipping-fee"},
}
)
このBigQueryへのロジックにモックが使われている場合、たとえ誤ったクエリの修正をしてもテストで検知できません。
インテグレーションテストより上位のテスト、つまりE2Eテストにおいて正常系のテストをしている場合はそちら側で検知できるかもしれません。しかしこのシステムでは、実装コストと得られるメリットを考慮し必要性は高くないという判断の下、E2Eテストを備えていません。
テストコードにおいて、BigQueryへのクエリが返す値にモックを使うこと自体は一部有用なケースはあるものの、本件については下記の点からモックを用いるのではなく実際にBigQueryへクエリを実行するほうが適切ではないかと考えました。
- 数十行にわたる複雑なクエリであり、実際にクエリを実行して確認したい
- 会計レポートの作成機能は財務諸表に影響する重要なアウトプットであり、時間をかけてでも堅牢にしたい
これらの背景をチームに共有し合意を得て、会計レポート作成機能のインテグレーションテストの改善を進めることにしました。
BigQuery Emulatorの導入
インテグレーションテストでは、BigQuery Emulatorを導入しました。
BigQuery Emulatorとは
まずBigQuery Emulatorとは、@goccyさん個人によって作成されているローカルで起動できるBigQueryのサーバーです。2023年4月現在betaではありますが、BigQuery APIなど多くの機能が実装されています。
クライアントの視点からは、BigQueryに近い動作をするサーバであるため、クエリ先が本物のBigQueryかBigQuery Emulatorであるかを気にせず透過的に扱えるため、テスト用途などで有用です。
内部実装について詳しく知りたい方は下記のスライドをご参照ください。
BigQueryエミュレータの作り方
BigQuery Emulatorの導入の背景
インテグレーションテストはCI上で並列で実行されるケースが多いです。そのため、クリーンアップやデータの挿入、更新のしやすさを考慮し、テストのプロセスごとにBigQueryの環境を用意しやすいBigQuery Emulatorを利用することに決めました。
また、インテグレーションテストにおいて使用するDBを、BigQuery Emulatorではなく本物のBigQueryとする案も検討しましたが、下記の点から見送りました。
- 1つのインスタンスのみを用意する場合、複数のテストが並列で実行されるため、ロックの処理が必要となり制御が複雑となる
- テストのプロセスごとにインスタンスを用意する場合、GCPのコストが上がり続ける
BigQuery Emulatorによるテストコードの改善
導入にあたり、一部のリファクタリングを実施しました。DI(Dependency Injection)を用いて、次のようにBigQueryへのクエリ先を切り替えられるようにしています。
- テスト実行時は、BigQuery Emulatorに接続する
- 本番時は、本物のBigQueryへ接続する
その後は、地道にモックが使われているコードを上記の方針で修正する作業を繰り返し、10数個のPRを作成して完遂しました。
工夫した点として、CIによるテストでは、BigQuery Emulatorだけではなく実際のBigQueryを使うテストも併用しています。Pull Requestをmainブランチにマージした後に実行するテストでは、テスト用の本物のBigQueryにクエリを実行しています。これはBigQuery Emulatorではエラーにならないが、本物のBigQueryではエラーになるなど、両者の環境差異によるエラーを検知するための予防措置としての意味を持ちます。
改善した結果と課題
テストの信頼性と開発速度の向上
インテグレーションテストでモックを使用する場合は、モック部分のロジックの信頼性を担保できない問題があります。しかし、BigQuery Emulatorを使用することで、実際の環境に近い形でクエリを実行でき、モック使用時と比較するとテストの信頼性を向上できます。例えば、クエリのシンタックスにエラーが含まれる修正が行われた場合に、CIでのテストによる検知が可能です。
テストの信頼性が向上することで、ソースコードへの変更を容易にしリファクタリングや機能追加の速度向上も期待できます。
まだ今回の改善後にレポートの改修要望はなく効果を発揮できているわけではありませんが、今後の改修に備えて基盤を整えることが出来たと思います。
今後の課題
今回の作業では、対処しきれない課題がいくつか残りました。
- インテグレーションテストをするために本来パッケージ外部へ公開する必要のない関数を公開している
- BigQuery用のクライアントコードがリポジトリ内に重複している
これらもタイミングを見て対応していく予定です。
おわりに
本記事では、BigQueryへのクエリを伴うインテグレーションテストにおける改善とその背景について紹介しました。
自動テストにおけるデータベースの扱い方は多くのソフトウェアエンジニアが抱える課題かと思います。皆様に何か1つでも新たな気付きをご提供できていれば幸いです。
最後に、テストに対する考えを整理するのに役立った書籍を紹介します。
単体テストの考え方/使い方 | マイナビブックス
https://book.mynavi.jp/ec/products/detail/id=134252