-
レポジトリをクローン
git clone https://github.com/autotaker/LegacyEx.git
-
Eclipseを起動し
File > Import > Gradle > Exsting Gradle Project
からプロジェクトをインポート -
プロジェクトを右クリックして
Run As > Run on Server
から起動- Server選択画面ではTomcat 8.5を選択
-
http://localhost:8080/LegacyEx/legacy/ にアクセス (末尾のスラッシュがないとうまく動かないので注意!)
ログイン画面に表示される挨拶メッセージを変更するオプション機能を追加してください。
- サーバ設定の
BIRTHDAY_GREETING_MESSAGE
が1
の時にオプションが有効化される。 - ユーザの誕生日のときには
Happy Birthday ${username} san
と表示する(${username}
はユーザ名に置換される) - ユーザの誕生日でない時には
Nice day ${username} san
と表示する - ユーザの言語設定に応じて表示メッセージの言語を変える
- 日本語の場合は
誕生日おめでとう ${username} さん
,ごきげんよう ${username} さん
- 日本語の場合は
レガシーコードに新規機能を追加したいが、追加するメソッドに仕様化テストがなく、新たに書くのも難しい場合に使えるテクニックです。
- 変更したい部分だけを別メソッドに切り出す。(Sprout Method)
- 変更対象クラスのインスタンスをテスト時に生成できない場合、新規クラスを作成し抽出したメソッドを移動する。(Sprout Class)
- 抽出したメソッドに仕様化テストを書く
- メソッドをテスト駆動開発で修正する。
対象コード中にシングルトンクラスを利用している場合、そのままではモックすることは難しいです。
public void someMethod(..) {
String option = GlobalConfig.instance().get("PARAMETER", "0");
}
このような場合は、シングルトンの制約をテスト時に緩め、モックインスタンスに入れ替えれるようにしましょう。
-
シングルトンクラスの
final
修飾を除去する -
シングルトンクラスに
setInstance(Singleton obj)
メソッドを追加する。このメソッドはテスト時にのみ使うということを明示するために
@VisibleForTesting
アノテーションをつけましょう。@VisibleForTesting public static void setInstance(GlobalConfig obj) { _instance = obj; }
-
テストの
setUp()
メソッドで、モックインスタンスに差し替える@Mock GlobalConfig globalConfig; @BeforeEach public void setUp() { ... GlobalConfig.setInstance(globalConfig); }
-
重要
tearDown()
メソッドでsetInstance(null)
を呼び出す。これをしないと他クラスのテスト時にモックインスタンスが残っていて意図しない挙動を引き起こす場合があります。
@AfterEach public void tearDown() { GlobalConfig.setInstance(null); }
staticメソッドを多用している場合、そのクラスのWrapperクラスを作成しましょう。
-
元々のクラス名+Wrapperの名前のクラスを作成する
public class DictWrapper { }
-
コピー元メソッド宣言をコピーし、staticを外す
public String get(String lang, Message message) { }
-
コピー元メソッドをそのまま呼び出す
public String get(String lang, Message message) { return Dict.get(lang, message); }
-
利用クラスのフィールドにWrapクラスのインスタンスを保持する.
final
をつけておくと初期化忘れを防げるので可能な限りつける。public class SomeClass { private final DictWrapper dict; }
-
コンストラクタの引数でWrapperクラスを受け取れるようにする。
public SomeClass(DictWrapper dict) { this.dict = dict; }
-
デフォルトコンストラクタではデフォルトのインスタンスで初期化する
public SomeClass() { this(new DictWrapper()); }
こうすることでProductionコードでは依存クラスの初期化をしなくて良くなる。
依存関係が複雑な場合はDIコンテナを利用する。
-
テスト時にMockインスタンスに差し替える。
@InjectMocks SomeClass it; @Mock DictWrapper dict;