Javaレガシーコードを扱ってから数か月が経ちましたが、これは私が扱っていることの一部です:
- 0% のテスト カバレッジ。
- 場合によっては、300 行を超えるコードを含む巨大な関数も見ました。
- 多くのプライベート メソッドと、場合によっては静的メソッド。
- 非常に緊密に結合されたコード。
最初は非常に戸惑いました。レガシーで TDD を使用するのは難しいと感じました。カタを何週間もやり、単体テストとモッキングのスキルを練習した後、恐怖心が減り、少し自信がつきました。最近、私は次のような本を見つけました: Working effectively with legacy , 私はそれを読んでいませんでした. 目次を見て、私にとって新しい何かを発見しました, The Seams. どうやらこれは、レガシーで作業するときに非常に重要です。
この Seams は、依存関係を壊してコードをテスト可能にするのに大いに役立つので、コード カバレッジを増やして単体テストをより正確にすることができると思います。
しかし、私は多くの疑問を持っています:
- シームとモックの違いを誰か説明してくれませんか?
- Seams は、テスト前に製品コードに触れないという点で TDD ルールを破っていますか?
- Seam と Mock を比較する簡単な例を教えてください。
以下に、コードをテスト可能にし、最終的にテスト カバレッジを増やすことを目標に、依存関係を壊そうとした今日の例を貼り付けたいと思います。間違いなどありましたらコメントいただけると嬉しいです?
これは、レガシー コードが最初にどのように見えるかです。
public class ABitOfLegacy
{
private String sampleTitle;
String output;
public void doSomeProcessing(HttpServletRequest request) {
String [] values = request.getParameterValues(sampleTitle);
if (values != null && values.length > 0)
{
output = sampleTitle + new Date().toString() + values[0];
}
}
}
そのメソッドを呼び出してその変数出力をアサートする単体テストを追加するだけで、呼び出し後に特定の値がある場合、私は単体テストを行っていないため、統合テストを行っているため、間違いを犯すことになります。だから私がする必要があるのは、パラメーターにある依存関係を取り除くことです。そのために、パラメーターをインターフェースに置き換えます。
public class ABitOfLegacy
{
private String sampleTitle;
String output;
public void doSomeProcessing(ParameterSource request) {
String [] values = request.getParameters(sampleTitle);
if (values != null && values.length > 0)
{
output = sampleTitle + new Date().toString() + values[0];
}
}
}
インターフェイスは次のようになります。
public interface ParameterSource {
String[] getParameters(String name);
}
次に行うことは、そのインターフェイスの独自の実装を作成することですが、HttpServletRequest をグローバル変数として含め、HttpServletRequest のメソッドを使用してインターフェイスのメソッドを実装します。
public class HttpServletRequestParameterSource implements ParameterSource {
private HttpServletRequest request;
public HttpServletRequestParameterSource(HttpServletRequest request) {
this.request = request;
}
public String[] getParameters(String name) {
return request.getParameterValues(name);
}
}
この時点まで、製品コードのすべての変更は安全だったと思います。ここで、テスト パッケージに Seam を作成します。よく理解していれば、Seam の動作を安全に変更できるようになりました。これは私がそれを行う方法です:
public class FakeParameterSource implements ParameterSource {
public String[] values = {"ParamA","ParamB","ParamC"};
public String[] getParameters(String name) {
return values;
}
}
そして最後のステップは、Seam からサポートを得て、メソッドの元の動作をテストすることです。
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import code.ABitOfLegacyRefactored;
import static org.hamcrest.Matchers.*;
public class ABitOfLegacySpecification {
private ABitOfLegacy aBitOfLegacy;
private String EMPTY = null;
@Before
public void initialize() {
aBitOfLegacy = new ABitOfLegacy();
}
@Test
public void
the_output_gets_populated_when_the_request_is_not_empty
() {
FakeParameterSource fakeParameterSource = new FakeParameterSource();
aBitOfLegacy.doSomeProcessing(fakeParameterSource);
assertThat(aBitOfLegacy.output,not(EMPTY));
}
@Test(expected=NullPointerException.class)
public void
should_throw_an_exception_if_the_request_is_null
() {
aBitOfLegacy.doSomeProcessing(null);
}
}
これにより、100% のテスト カバレッジが得られます。私はあなたの考えに感謝します:
- 依存関係を正しく壊しましたか?
- 単体テストに何か欠けているものはありますか?
- 何がもっとうまくできるでしょうか?
- この例は、Seam と Mock の違いを理解するのに十分ですか?
- Seam を使用しない場合、ここでモックがどのように役立つのでしょうか?