ユニットテスト可能なコードに変換するには「接合部」をマスターしよう〜 技術的負債とダンスを(6)
『レガシーコード改善ガイド』を理解する際の肝となる「接合部(Seam)」をコード例で理解しよう!!
はじめに
前回から『レガシーコード改善ガイド』の解説しています。今回は、『レガシーコード改善ガイド』を理解する際の肝となる「接合部」について、改善のコード例を示しながら解説します。
接合部とは?
『レガシーコード改善ガイド』によると、接合部、許容点の定義は次です。
「接合部(seam)とは、その場所を直接編集できなくても、プログラムの振る舞いを変えることができる場所である」
「どの接合部にも許容点(enabling point)を持つ。許容点では、どの振る舞いを使うかを決定できる」
接合部にはいくつか種類があり、オブジェクト指向言語を使っている場合はオブジェクト接合部がとても役立ちます。プロジェクトで採用しているのがオブジェクト指向中心ではなく、ファンクション中心の場合も接合部と許容点を作って、ユニットテストできるコードに変換することが可能です。
下記は JavaScript における接合部の振る舞いの差し替え例です。
テストが難しいコード例
下記の judgeChohanファンクションは、2つのサイコロで丁(2つのサイコロの目の和が偶数)か半(奇数)かを判定するプログラムです。が、サイコロの1–6のランダムの振る舞いが密に結合して、接合部・許容点がなくユニットテストが難しくなっています。
では、テスト可能なコードに変換するにはどうすればよいのでしょうか?
A:ファンクションの抽出 & 引数で差し替え
サイコロのランダム要素のためにテストを難しくている箇所を、リファクタリング「ファンクションの抽出」を行い、引数を使って置き換えられるようにします。
上の修正後の2行目のdiceTotal()が編集なしに振る舞いを変えることができる接合部、judgeChohanの第二引数によるdiceTotalの置き換えできる箇所が許容点に相当します。diceTotal()は乱数をデフォルトで返す一方、テストコード側からは引数を使って丁(偶数)と半(奇数)の場合のテストを可能にしています。「ファンクションの抽出」を行い、引数を使ってテストから置き換えられるよう接合部と許容点をつくることは、単純ながら強力な武器になります。
B:モジュールの抽出&Jestによるモジュール差し替え
ファンクションを別ファイルの別モジュールに移動している場合は、引数で振る舞いを差し替える代わりに、テスティングフレームワークの Jestの機能を使って差し替えができます。JavaScriptの場合は、モジュールによって振る舞いを切り替えできる特徴を持っています。「ファンクションの抽出」の後に「ファンクションの移動」を慎重に行って、テストしやすいコードに変換してテストを記述します。
上のコードは引数による許容点はなくなっています。が、下のコードのように テスティングフレームワークのJestの機能を使って、丁の場合と半の場合にdiceTotal()が返す値の振る舞いを変えています。
C:ファンクションの生成と引数で差し替え
ファンクションを直に定義する代わりに、ファンクションを返すファンクションを定義し、デフォルトは乱数で、テストからは丁の場合と半の場合の振る舞いを変えてテストしています。(変更後の1行目のコードの読み方については、こちらも参考にしてください。)
オブジェクト指向の場合
プロジェクトが採用しているのがclassベースのオブジェクト指向の場合は継承による差し替え、移譲とインジェクションによる差し替えという定番のアプローチがあります。
D:メソッドの抽出&継承とオーバライド
少々トリッキーに見えますが、ランダムの差し替えたいコード箇所を「メソッドの抽出」ができれば、あとは継承でオーバイライドすることでテストできるようになります。
E:移譲&コンストラクタのインジェクション
継承ではなく移譲を使って制御もできます。この例では、コンストラクタのインジェクションを使ってテスト可能にしています。下記は、『レガシーコード改善ガイド』の「コンストラクターのパラメータ化(394)」を少々崩した内容になっています。
Javaでよく見かけるイディオムのインタフェース抽出し、インタフェースと実装の分離を使って、diceTotal()をランダムな振る舞いとテスト用の振る舞いの差し替えができる構造変換までは行っていません。
まとめ
レガシーコードの場合、ユニットテストを記述しようにも、テストを難しくするコードとテストしたいコードが密結合しているためにユニットテストを難しくしている場合があります。ユニットレベルのテスト容易性を向上させるためのよくある1手としてリファクタリングステップの「ファンクションの抽出」もしくは「メソッドの抽出」が地味ながらもとても強力です。今回は数例を示しましたが、『レガシーコード改善ガイド』では、依存関係を排除して、ユニットテスト&リファクアリングできるようにする方法をC++ &Javaのサンプルコードで多数挙げています。
今回の例ではサイコロのランダムな振る舞いがテストを難しくしているため分離したい箇所でした。テストの際に置き換えたい箇所は、ランダムのほか、時間など環境依存、外部のAPI呼び出し、永続化層へのアクセス、特定の技術要素、古いドメイン層などがよくあるパターンです。
次回は、『レガシーコード改善ガイド』が提供している改善テクニックの語彙体系全般を紹介します。