ここで私のアプローチ。4段階のリファクタリングテストであるため、時間の面でコストがかかります。
私が公開しようとしているものは、質問の例で公開されているものよりも複雑なコンポーネントの方が適している可能性があります。
とにかく、この戦略は、インターフェイス(DAO、サービス、コントローラーなど)によって正規化されるコンポーネント候補に対して有効です。
1. インターフェース
MyDocumentServiceからすべてのパブリックメソッドを収集し、それらをすべてインターフェイスにまとめることができます。例えば。既に存在する場合は、新しいものを設定する代わりに、その1つを使用します。
public interface DocumentService {
List<Document> getAllDocuments();
//more methods here...
}
次に、MyDocumentServiceにこの新しいインターフェイスを実装させます。
ここまでは順調ですね。大きな変更は行われず、現在の契約を尊重し、behaivosは変更されていません。
public class MyDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//legacy code here as it is.
// with no changes ...
}
}
2. レガシーコードの単体テスト
ここに大変な仕事があります。テストスイートをセットアップします。できるだけ多くのケースを設定する必要があります。成功したケースとエラーのケースです。これらは最後に結果の品質のためです。
次に、MyDocumentServiceをテストする代わりに、テストするコントラクトとしてインターフェイスを使用します。
私は詳細には立ち入らないので、許してくれ
public class DocumentServiceTestSuite {
@Mock
MyDependencyA mockDepA;
@Mock
MyDependencyB mockDepB;
//... More mocks
DocumentService service;
@Before
public void initService(){
service = MyDocumentService(mockDepA, mockDepB);
//this is purposed way to inject
//dependencies. Replace it with one you like more.
}
@Test
public void getAllDocumentsOK(){
// here I mock depA and depB
// wanted behaivors...
List<Document> result = service.getAllDocuments();
Assert.assertX(result);
Assert.assertY(result);
//... As many you think appropiate
}
}
この段階では、このアプローチの他のどの段階よりも時間がかかります。そして、それは将来の比較のための参照点を設定するため、最も重要です。
注:大きな変更は行われなかったため、動作は変更されていません。ここでSCMにタグを付けることをお勧めします。タグやブランチは関係ありません。バージョンを実行するだけです。
ロールバック、バージョン比較、および古いコードと新しいコードの並列実行用に必要な場合があります。
3. リファクタリング
リファクタリングは新しいコンポーネントに実装されます。既存のコードに変更を加えることはありません。最初のステップは、コピー&ペーストを行うためにするのと同じくらい簡単ですMyDocumentServiceとに名前を変更CustomDocumentService(例えば)。
新しいクラスはDocumentServiceを実装し続けます。次に、getAllDocuments()をリファクタリングします。(1から始めます。ピンリファクタリング)
DAOのインターフェース/メソッドの変更が必要になる場合があります。その場合、既存のコードを変更しないでください。DAOインターフェイスに独自のメソッドを実装します。古いコードにDeprecatedの注釈を付ければ、後で何を削除すべきかがわかります。
既存の実装を壊したり変更したりしないことが重要です。両方のサービスを並行して実行し、結果を比較する必要があります。
public class CustomDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//new code here ...
//due to im refactoring service
//I do the less changes possible on its dependencies (DAO).
//these changes will come later
//and they will have their own tests
}
}
4. DocumentServiceTestSuiteの更新
さて、今は簡単な部分です。新しいコンポーネントのテストを追加します。
public class DocumentServiceTestSuite {
@Mock
MyDependencyA mockDepA;
@Mock
MyDependencyB mockDepB;
DocumentService service;
DocumentService customService;
@Before
public void initService(){
service = MyDocumentService(mockDepA, mockDepB);
customService = CustomDocumentService(mockDepA, mockDepB);
// this is purposed way to inject
//dependencies. Replace it with the one you like more
}
@Test
public void getAllDocumentsOK(){
// here I mock depA and depB
// wanted behaivors...
List<Document> oldResult = service.getAllDocuments();
Assert.assertX(oldResult);
Assert.assertY(oldResult);
//... As many you think appropiate
List<Document> newResult = customService.getAllDocuments();
Assert.assertX(newResult);
Assert.assertY(newResult);
//... The very same made to oldResult
//this is optional
Assert.assertEquals(oldResult,newResult);
}
}
これで、oldResultとnewResultの両方が独立して検証されましたが、相互に比較することもできます。この最後の検証はオプションであり、結果に依存します。比較できないかもしれません。
この方法で2つのコレクションを比較するにはあまり見かけませんが、他の種類のオブジェクト(pojo、データモデルエンティティ、DTO、ラッパー、ネイティブタイプなど)には有効です。
ノート
単体テストの実行方法やモックライブラリーの使用方法を敢えて教えません。リファクタリングをどのように行わなければならないかを言うつもりもありません。私がやりたかったのは、グローバル戦略を提案することです。それをどう進めるかはあなた次第です。コードの仕組み、複雑さ、およびそのような戦略が試してみる価値があるかどうかを正確に知っています。ここでは、時間やリソースなどの事実が重要です。また、今後これらのテストから何を期待するかも重要です。
私はサービスによってサンプルを開始しましたが、DAOなどをフォローします。依存関係レベルに深く入ります。多かれ少なかれ、それはアップボトム戦略として説明できます。ただし、マイナーな変更/リファクタリング(ツアーの例で公開されているものなど)の場合、ボトムアップでタスクが簡単になります。変更の範囲が少ないためです。
最後に、廃止されたコードを削除し、古い依存関係を新しい依存関係にリダイレクトするのはあなた次第です。
廃止されたテストも削除し、ジョブは完了しました。テストで古いソリューションをバージョン管理した場合は、いつでも相互に確認および比較できます。
非常に多くの作業の結果、レガシーコードのテスト、検証、およびバージョン管理が行われます。そして、テスト、検証、およびバージョン化の準備ができた新しいコード。