初投稿です。
これまであまり技術記事は書いたことありませんでしたが、良い機会だと思ったので書いてみることにしました。
書き慣れていないということもあり、言葉が足りておらず何言ってるかわからなかったり、色々と読みにくい部分も多いかと思いますが予めご了承ください。
★まず最初に
システム提供側としては、古いバージョンの言語の課題やサーバのファームウェアアップデート等、様々な懸念があるかと思います。
最初からバージョンアップしやすいような作りや障害に強い構成にしておけばいいと言われてしまうとそれまでですが、昔から運用されているシステムでは、沢山の機能や外部連携サービスが多かったり、当時を知るエンジニアが既に居なかったりで、なかなかリニューアルにも踏み切れない上に、言語のバージョンアップでもどのような影響があるのかが読めずになかなか勇気がいります。
そのようなシステムで、いかにユーザ影響がないようにバージョンアップを行うにはどのようなアプローチがあるのか?を書いていきたいと思います。
使用しているミドルウェアの具体的な使い方等は別の機会で書きたいと思います。
★書いている内容
- 古いプログラム言語からのバージョンアップにおいて、どうすればユーザ影響なくバージョンアップ出来るのか?
- 既存のプログラムはそのままで、どうやってメール送信や外部サービスのE2Eテストを作成するのか?
★バージョンアップにおいて必ず保証しなければならないことは?
一言でいうと、バージョンアップ前後でシステムが 【同じ挙動】をしていることだと思っています。
システムが同じ挙動とは、下記のようなものが挙げられます。
- 検索機能にて同じデータが検索・表示される。
- 新規登録・編集で同じデータが登録される。
- 作成されるファイル(CSV,PDF,メール等)の中身が同じである。
- FTPで外部に送るファイルが同じである。
- 外部へのAPIリクエストが同じである。
- 外部からのレスポンスが同じであれば、その後のシステムの動きも同じである。
等
★その確認ってどうやるの?
【その1】 手動検証する
- テスト仕様書を作るだけでも大変。
そもそもどうなったら正解なのか? がわからない。
大量の機能の仕様を調べるところからはじめないといけない。 - 常に開発は進んでいるもの。
プログラムに修正があったら検証はやり直しなのでは? - 次のバージョンアップの時も人力で乗り切るのか?
将来的なことを考えると、極力手動は避けたほうがいいでしょう。
【その2】 ユニットテストで保護
基本的には、旧バージョンでユニットテストを書き、バージョンアップ後に動かなくなる箇所を修正するのが良いと思いますが、
歴史のあるシステムだと、一つの処理が大きすぎてテストを書くのが難しい箇所も出てきます。
ex.) 主な要因は、条件分岐が多い。前提となるデータ準備が大変等。
↓
テストコードを書くには処理を細かくわけたり、リファクタリングしないといけない。
↓
影響範囲がわからなくて怖すぎて直せない orz...
(そういう処理に限って、沢山のところで使われていて、何かが起きると重大な障害に繋がってしまうものが多い)
ではどうすればいいのでしょうか?
【その3】 E2Eテストで保護 ※この記事の本題
★E2E(End To End)テストとは?
- 一般的には、フレームワークを使って
「実際のブラウザでの動きをシミュレーションするようなテスト」という意味合いで使われることが多い。 - ブラウザ側の UI や Ajax 通信を含めたWebアプリケーション全体を通したテスト
★ユニットテストとE2Eテストの役割
ユニットテストの役割
- その部品が正しく動くことを担保する。
ただし、バグがあるかどうかは、部品が正しく動くこととは別の問題。 - 部品はひとつひとつの機能をなるべく小さくて、
再利用性に長けている必要がある。 - 十分にテストされた部品は、安心して利用することができる。
E2Eテストの役割
- ユーザが実際にどのように画面を操作するか、という観点でテストが行われる。
- 複数の部品が組み合わされたものを通しでテストする。
- システム全体が正しく動作することを確認する。
E2Eテストのデメリットとしては
ユーザの実際の操作をなぞるため、ユニットテストに比べて、実行時の時間がかかることが多く、全ての機能にE2Eテストを実装すると、実行するだけで時間を取られてしまいます。
基本的にはユニットテストがきちんと書かれているほうが嬉しい!
ですよね。
★歴史のあるシステムでE2Eテストを書く時に困ること
この機能がどのように動くのか正解がわからない
(だって、沢山機能あるし、、、業務フローが難しいし、、、)
正解とは?
厳密にやるなら、
この機能を使うと、「どのテーブル」の「どのカラム」の「どういうデータ」が「こういうデータ」に変わる
ということが明確になっていてそれを結果とすることが理想です。
しかしそれをやるには、プログラムを隅まで追って、データの流れを全て掴むことが必要です。
うーん、ちょっと大変そうです。
★旧バージョンでの動作 = 正解の動作である と定義
旧バージョンで操作した時のDB更新内容、作成ファイル、通信内容(FTPやAPI)等を全て正解データとして保存する。
↓
バージョンアップ後に、同じ操作を行い、正解データと比較する。 同じであればバージョンアップによる影響はないということ。
★E2Eテストにおける、外部サービスとの連携
通常の動き
実際に外部APIを叩いたり、接続したり、データ連携させています。
連携後は、同じデータを使うとデータの整合性が合わず、再度テストが行うことができないことも多いです。
外部サービス側からしても、E2Eテストが実行させる度に通信がくるというのは嫌でしょう。
E2Eテストの構成
正解データを作成するために、一度は実際に外部に連携しますが、
その後は、ドメインの名前解決 や iptables 等を駆使して、全て通信を内部に向けさせます。
※図がなくて申し訳ないです。文字だけだとわかりにくいかもしれません。
★E2Eで検証するデータ種類によって比較方法
この記事の最初のほうで触れている、それぞれのシステムの挙動で比較する方法・ツールは
下記のようなものがあります。
- 検索機能にて同じデータが検索・表示される。
→ 表示されたHTML同士の比較 - 新規登録で同じデータが登録される。
→ 「どのテーブルに変更が加わったのか?」は DBデータファイルの更新日時等から自動取得させる。
→ 登録されたDBデータの差分の比較。( DB Diff )
→ 比較後にテスト実行前の状態に戻す( DB Diff ) - 作成されるファイル(CSV,PDF,メール等)の中身が同じである。
→ 基本的に文字列であれば diff コマンド等で比較
→ PDF は、pdftotext( yum の poppler-utils で入ります) でテキストにして比較
レイアウト崩れは、DiffPDF で拾える
→ メールは、mailcatcher でキャッチして比較 - FTPで外部に送るファイルが同じである。
→ 実際にFTPで送信後のファイル同士を比較
→ mockFTPServer を使用。 - 外部へのAPIリクエストが同じである。
→ WireMock を使用し、リクエストとレスポンスを記録して返却 - 外部からのレスポンスが同じであれば、その後のシステムの動きも同じである。
→ WireMock を使用し、レスポンスを固定化。
各種ツールのセットアップ手順や使い方は別の機会で書ければと思います。
★E2Eテスト実行環境について
- E2Eテストのテストフレームワークを別で立てることで、どんな言語で作られたWebアプリケーションに対しても、E2Eテスト作成が可能です。
- この記事は、「バージョンアップするためのE2Eテスト作成」に特化したアプローチなので、開発フローに組み込むのは別の話になります。
- 既存のシステムに対し、現状を正解と定義するE2Eテスト作成は可能です。
改修時に検証コストがかかるプロジェクトは、E2Eテストで一通り保護しておくと安心かもしれません。 - テストフレームワークは、PHPでは Codeception を使用することで、比較するデータ別にヘルパーを独自作成して使う等もできます。
他にもフレームワークはあるので機会があれば使ってみたいと思います。
★おわりに
歴史のあるシステムというのは、今でこそ色々と複雑に絡み合いなかなか難しいものとなってしまっているかもしれませんが、変化の激しいこの世の中のその時々で最善を尽くしてきた証でもあると信じています。
様々な状況の中で品質よりスピードが優先されることも多かったと思います。
沢山の人員入れ替えがあり、スキルもバラバラな人が沢山携わってきたんだと思います.
同じ過ちを繰り返さないという点では過去を振り返ることは大切だと思いますが、
ただ現状に文句を言ったところで何も変わりません。ストレスも溜まります。
素直に今を受け入れ、これまで携わった人に対してのリスペクトを常に持ち続けながら
出来る限り遠い未来も見据えた上で、より良いシステムを作るという心を私は持ち続けたいと思います。