レガシーコード改善とテスト駆動の違い

皆様、レガシーコードとの戦いは順調ですか?(笑)
世にはびこるレガシーコードがある限り、我々の仕事は終わりません。
レガシーコード改善はTDDと少し違うのかなぁ?という自分の中の思いが生まれてきたので、ここに記しておきます。

TDD

テストコードの作成 (red)

イメージした仕様のテストを書く。

プロダクトコードの作成 (green)

テストが通るようにテストを書く

リファクタリング (red -> green -> red -> green…)

コードが綺麗になるように変えていく

レガシーコード改善

仕様の把握 ※特にインプットとアウトプットの洗い出し

単体テストのTDDの場合、小さな部分の動きをテストで固定していきます。所謂単体レベル。
しかし、レガシーコードは単体レベルで書き換えられるようになっていないことが多いです。
まずそのコードが依存している部分で、全体の仕様を洗い出すことが必要です。
特に抑えるべきは、インプットとアウトプットを洗い出しです。
おそらくこれが一番時間がかかる大変な作業です。

fixtureの作成

洗いだしたfixtureを作成していきます。fixtureというのは、データベースの値に限りません。ファイルを作成するのであれば、ファイルも、ブラウザに出力するのであれば、ブラウザへの出力の値もです。
本来であれば、1つの関数やクラスに閉じているはずの条件分岐を全体で洗い出す必要があります。

e2eテストの作成

エンドツーエンドのテストの作成。
input fixtureを使って、output状態が保証されることを示すテストを書きます。
私はこのタイミングでモックはできるだけ使わないようにしています。
モックをしてしまうと、モックを外したときに動作が変わってしまう恐れがあります。モックをしてテストが通ってしまうくらいなら、テストが落ちてくれる方が安心です。

e2eテスト結果から仕様漏れを洗い出す

e2eテストの結果はコードカバレッジとして出力します。しかしながら、単体レベルの動作でないわけです。ループ文が何十にも通ることで、カバレッジ率が高くでてしまったりすることもあるかと思います。コードカバレッジはあくまで参考程度にしかなりません。
コードカバレッジで明らかに漏れているインプットがあれば、仕様に追加していきます。

仕様漏れの追加 -> e2eテスト -> 仕様漏れの追加 -> テスト …

上記の作業を繰り返します。

 リファクタリング

e2eテストが落ちないことを確認しながら、小さな動作の塊にテストを書き、関数やクラスに切り出していきます
TDDのリファクタリングと変わりませんが、テストコードを増やしリファクタリングしていく。ここの中身はTDDと何も変わらない。
しかしながら、ここでバグや仕様漏れを大量に発見することになるはずです。その度に仕様を追加して、リファクタ前のコードでe2eテストをする必要があります。

レガシーコード改善とTDDの違い

レガシーコード改善の方がはるかに重いですが、基本的な考え方はどちらもTDDにもとづいているなぁと振り返りまとめなおして感じました。

レガシーコードとはテストがないコードのことを言うと言われています。
しかしながら、現実的な問題から全てのコードにテストがあるプロジェクトは少ないのではないでしょうか。
レガシーコードが問題になるのは、仕様が一見しにくいほど、複雑な処理を行っているにも関わらず、クラスや関数が適切な形で分割されていないからでしょう。
そして、レガシーコードの真の仕様はテストに記載されていないため、実機のみが知るという状況になっているかと思います。
その状況を改善するためには、仮説の仕様からテストを実装していき、その他の仕様を洗い出していく必要があるかと思います。これは大変な作業なので、可能な限り小さな単位のテスト(単体テスト)をTDDで行い。仕様変更に強いコードを作成していく必要があるのだと思います。
それではhappyコーディングライフを!


レガシーコード改善ガイド (Object Oriented SELECTION)


テスト駆動開発入門