背景
経費精算システム「楽楽精算」は2009年にリリースされ、15年以上にわたり運用されてきました。 その間、基本的なシステム設計はリリース当初のまま維持されています。 しかし、年月が経つにつれ、技術トレンドやビジネス的な要求は大きく変化しましたが、現状のシステムではそれらの変化に柔軟に対応することが困難になってきています。 システムの柔軟性は低く、機能追加のたびに既存機能への影響を広範に調査する必要があり、既存の処理フローを変えることができないため、イレギュラーなテクニックが必要となることも多く、追加開発のたびに多くの手間とコストがかかるようになってきました。
すべての問題が現行システムに起因するわけではありませんが、特定のミドルウェアに強く依存した構造を持っているため、将来的な技術革新や新しいミドルウェアへの移行が困難であるという課題も抱えていました。 このような背景から、ミドルウェアに依存しない中立的なアーキテクチャが必要であり、その実現のためにリアーキテクチャに近い大規模なリファクタリングが計画されました。
その際に最も懸念されたのは、リファクタリングによる動作不具合のリスクです。 現状では、すべての動作を保証する自動テストが存在しないため、このリスクを無視することはできませんでした。 加えて、仕様書は完全には整備されておらず、初期リリース時から積み重なった変更仕様が複雑に絡み合い、システム全体の動作を統一的に把握することが困難な状況にありました。 このような状況下で、リファクタリングを成功させるためにはどのような対策が必要かが、プロジェクトの大きな課題となりました。
このプロジェクト「リファクタリングに向けた自動インテグレーションの実装」は、こうした背景を踏まえ、動作を保証するためのインテグレーションプロセスを自動化し、リファクタリングを安心して進められる環境を整備することを目指しました。
使用した技術とツール
プロジェクトで中心的な役割を果たしたのは、AOP(Aspect-Oriented Programming)、JUnit、そしてDBUnitという3つの技術です。
リファクタリングを行う際に最も重要なのは、動作に変更がないことです。 そのためには、現行のシステムがどのように動作しているかを正確にトレースする必要がありました。 システムの動作は単純に言えば入力と出力です。 エンドユーザーの操作によってシステムに情報が入力され、その結果としてデータベースや画面に情報が出力されます。 操作時に呼び出されるエンドポイントへの入力と出力、そしてその前後でデータベースの状態がどのように変化しているかを記録する必要がありました。 しかし、これを手動で行うのは非常に手間のかかる作業です。 そこで、AOPを用いてエンドポイント呼び出し前と呼び出し後の状態を自動的に取得する仕組みを構築しました。 これにより、通常の操作を行うだけでテストデータが自動的に蓄積されていく仕組みが出来上がりました。
次に、取得したデータを自動テストに活用するために、JUnitとDBUnitを選定しました。 まず、取得したテストデータから事前のデータベースの状態をDBUnitでリストアします。 JUnitでテストデータの入力情報をエンドポイントに入力することで処理が行われ、データベースの変更と画面出力が行われます。 それらの結果をDBUnitとJUnitを用いて事後のテストデータと比較することで、動作を検証します。
JUnitはJavaでの単体テストの標準フレームワークであり、楽楽精算ではすでにユニットテストで使用していたため、その知見を活かせることが選定理由です。 DBUnitは、データベースの状態をテストケースごとに再現するためのツールであり、データベースを利用したシステムテストにおいて非常に有効です。 この2つのツールを組み合わせることで、取得したデータを効果的にテストに利用し、リファクタリング後のシステムの安定性を保証することができました。
直面した課題
プロジェクトを進める中で、最も困難だったのは、現行のモジュール構成とリファクタリング後のモジュール構成が互換性を持たない可能性が高かったことです。 このため、単体テストの範囲を限定することが難しく、ほぼすべてのケースをインテグレーションテストでカバーする必要がありました。 特に、仕様書が不完全であったことから、どの部分を優先的にテストすべきかを判断するのが非常に困難でした。
テストケースの作成とテストデータの取得にも多くの時間と労力を要しました。 テストケースの作成からテストデータの収集までを本プロジェクトのチームのみで行うのは現実的ではなく、大きな課題でした。 特に特殊なケースについては製品に対する深い知識が必要であったため、外部委託は難しい状況でした。 その際に頼りになったのがオフショアチームでした。 ラクスはベトナムにも開発チームを有しており、10年以上にわたる開発経験があります。 楽楽精算の開発にも長く携わっているため、製品や仕様に精通しており、テストケースの作成からテストデータの収集までを一貫して行うことができました。 これにより、短期間で膨大な量のテストケースの作成とテストデータの収集を行い、プロジェクトを前進させることができました。
解決策と手順
このプロジェクトを成功させるために、いくつかの重要なステップを踏みました。
要件定義と技術選定
システム設計と開発
テストとデプロイ
- テストの段階では、広範囲にわたるテストケースを作成し、手動で収集したデータを活用してすべてのケースをカバーすることを目指しました。特に、リファクタリング後の動作が現行と一致するかを検証するため、各テストケースでデータベースの状態が正確に再現されることを確認しました。
結果
プロジェクトの結果として、私たちは広範な自動テストを構築し、リファクタリングによる不具合のリスクを最小限に抑えることができました。 自動化されたテストが存在することで、システムの変更に伴うリスクを可視化し、迅速に対応することが可能となりました。 この結果、リファクタリングの過程で発生する可能性のある多くの問題を事前に検出し、対応することができました。 実際のリファクタリングの際も、自動インテグレーションテストをTDDのように使用することで、着実にリファクタリングを進めることができました。
考察と今後の展望
このプロジェクトを通じて、自動テストの重要性と有効性をあらためて認識しました。 自動テストが存在することで、システムに対する信頼性が格段に向上し、リファクタリングやシステム変更の際の不安を大幅に軽減することができます。 これは、システムの継続的な変化が求められる現代においては、不可欠な要素です。
一方で、自動テストのメンテナンスコストが予想以上に高くなることも判明しました。 今回のリファクタリングのために作成した自動テストは、今後も機能開発時の自動テスト(主にリグレッションテスト)として活用したいと考えていました。 しかし、現状の自動インテグレーションテストは基本的に入出力の完全一致を確認するものであるため、入力や出力に変更がある場合はテストデータを修正する必要があり、 その作業コストが当初の想定以上に大きくなっています。 今後は、自動インテグレーションテストの検証範囲を制限し、ユニットテストで担保できる部分はそちらで対応するなど、新たなテストプロセスの構築が求められます。
最後に
このブログが、同様の課題に直面しているエンジニアにとって有益であることを願っています。