バッチ処理の自動テストを書こう
はじめに
こんにちは。webエンジニアの ningenMe です。
この記事は ソフトウェアテスト Advent Calendar 2021 の 21日目の記事です。
バッチ処理とは
バッチ処理の定義がちょっと曖昧なので、本文中におけるバッチ処理を定義しておきます。
ここでは あるデータソース A
から データを取得し、なんらかの処理をした後、あるデータソース B
に永続化するような作業、とします。
もちろんデータソースというのは、mysqlでもいいし、はたまた外部APIでもいいですし、s3などのオブジェクトストレージでも構いません。
インプットとアウトプットが外部にあって、それをやりとりするイメージです。
前提
ここで実装に関しては spring-batch と呼ばれるフレームワークで実装されたバッチ処理に対するテストを想定します。
テストのフレームワークは Junit5 です。
まず
テストを書きましょう。
テストのアドカレなのに何を言ってるんだと思われるかもですが、自分が見てきた限りだとバッチ処理に対してテストが薄かったり書かないみたいなチームや開発スタイルにしばしば遭遇します。
テストを書きましょう。
integrationテストを書こう
バッチ処理でよく見かけるのが、かなりの項目を結合テストで対処するパターンです。
もちろん結合テスト自体は大事なのですが、ビジネスロジックの細かい挙動や、IOのエラー時の挙動など、ありうるパターンを全部手作業でやろうとするとかなり大変です。
ましてやコード凍結した後にバグが見つかったら修正が入って結合テストが全部やり直し、なんてこともしばしば......。
なので、まずはソース内で完結させることができる部分だけでも極力自動テストを書いて、結合テストの負担を減らしましょう。
integrationテストで出来ることは極力、結合テストでやろうとしないこと
です。
integrationテストって何?
ここではアプリケーションを一気通貫で動かす自動テストを指しています。1クラスだけなどの単体テストではなく、バッチの実行単位(あるいは内部の1step)ごとのテストをイメージします。
結合じゃないとテストできない?
バッチでよく言われるのが、この話なのかなと思ったりしています。
DBを用意できないとか、データを用意できないとか。そういう理由で開発環境で手動テストをたくさんやるみたいな流れが生まれたり。
これは本当に良くない。
近年だと一般的なdbとかが大体dockerで手軽に立てることができます。
apiに関してもnginxなどをdockerで立ててしまえば、mockとしては十分です。
なのでテストの実行のたびにスタンドアローンなテスト環境(DBや外部のAPIのmock)を整えることができるはずです。
開発環境に立ってるdbなどを前提としたテストは、テストが状態を持ってしまうことや、冪等性、並列性を考えても本当によくないです。
スタンドアローンなテスト環境を作り、結合テストをやめて極力integrationテストを書きましょう。
例として spring-bootでのスタンドアローンなテストの作り方を載せておきます。
状態のassert
テストって基本的には、なんらかのインプットに対して、適切なアウトプットになっているかを確認する部分が大きいと思います。
バッチ処理のテストでも、なんらかのデータソースをインプットとし、最終的に永続化されるデータに対して assert が行われるべきです。
dbunitなどのツールを用いてdbのテーブルをassertしたりしましょう。
静的なファイルとのデータの突合を意識するべきです。
なんとなく正常終了したらok、ではダメです。
冪等性
バッチ処理において、冪等性はかなり大事です。
もちろん結合テストでも確認したいですが、これこそ自動テストでもある程度確認できるので、自動化しましょう。
以下にソースのイメージを載せます。
void test() {
//inputを準備する
setupInput()
//バッチを動かす
execBatch()
//outputをassertする
assertOutput()
//冪等確認のために再度バッチを動かす
execBatch()
//outputをassertする
assertOutput()
}
おそらくある程度ソースが書けていれば、上記のように手軽に確認できるはずなのでintegrationテストを自動化する際は入れたほうが安心感が増します。
コードフリーズ
リリースが近づいたら、コミットをこれ以上加えない、という日付を設けてコードをフリーズさせましょう。
上記でも話しましたが、開発途中で結合テストをするのは良くないです。
もちろん結合テストでバグが見つかって修正コミットをするのは普通のことです。
ただその時は、手動でやってるような結合テストは全部やり直すべきです。
最終的なソースの状態に対して、結合テストが行われるべきです。
実際のところ、コストとの兼ね合いもありなかなか上記が守られないことが多いです。
なので1ケースでも多く自動テストに組み込み、結合テストを減らし、CIが毎回チェックしてくれるような仕組みを作りましょう。
人間は疲れてくるとテストしなくなります。
個人的な話
自分の業務では、最初は手動の結合テストが多め、自動のintegrationテストはほぼない状態でプロジェクトを進めていました。
その時はバグがかなり多く出たり、後から改修が増えた結果、工数も大きくなりました。
途中でプロセス改善を行い、
手動の結合テストを少なく、自動のintegrationテストを多くしたところかなりバグが少なくなり、品質が大きく上がりました。
また結果的に工数も少なくなりました。
やっぱり自動テストに寄せる方が、全体としては品質も工数も担保されると思います。
さいごに
具体的なテクニックというよりは、プロセス改善に近い気はします。
Javaに関わらず、どの言語で書くにしてもある程度意識することは同じだと思います。
チーム内で誰かが(あるいは自分が)手作業でたくさんテストしてたら違和感を持ちましょう!
快適な自動テストライフを。
ではでは。