15

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 を使用しない場合、ここでモックがどのように役立つのでしょうか?
4

2 に答える 2

16

継ぎ目は、動作に変更を挿入できるコード内の場所です。依存関係の注入をセットアップするときに継ぎ目を作成しました。

縫い目を利用する 1 つの方法は、ある種の偽物を挿入することです。偽物は、例のように手で巻くか、Mockito などのツールで作成できます。

というわけで、モックはフェイクの一種で、フェイクはSeamを利用して使われることが多いです。

あなたのテストと依存関係を壊した方法に関しては、それは私がやった方法とほぼ同じです。

于 2013-03-11T13:59:29.820 に答える
12

継ぎ目

シームは、コードを変更せずに動作を変更できる場所です。

あなたの例では、以下はオブジェクトシームの例です(私が間違っていなければ)。コードを変更せずに別のオブジェクトを渡すことができます。したがって、それは縫い目の一種です。

public void doSomeProcessing(ParameterSource request) {..}

パラメーターを (具体的なクラスではなく) 抽象型にすることで、継ぎ目が導入されました。継ぎ目により、コードを編集せずにメソッドの動作を変更できるようになりました。つまり、呼び出しの場所で、別のオブジェクトを渡して、メソッドに別のことをさせることができます。

モック

カスタム フェイクを作成する (インターフェイスのサブタイプを作成する) 代わりに、モック フレームワークを使用して次のようなことを行うことができます。

モックは、特定のメソッドが呼び出されたかどうかのアサート、引数の一致、およびテストによって消費されるその他の気の利いた機能もサポートしています。維持するテスト コードが少なくなります。モックは主に、特定の呼び出しが依存関係に対して行われていることをアサートするために使用されます。あなたの例では、スタブが必要なようです。缶詰の値を返したいだけです。

私のさびたJMockを許してください..

 @Test
    public void
    the_output_does_not_get_populated_when_the_request_is_empty
    () {
        Mockery context = new Mockery();
        final ParameterSource mockSource = context.mock(ParameterSource.class)

context.checking(new Expectations(){{
    oneOf(mockSource).getParameters(); 
            will(returnValue(new string[]{"ParamA","ParamB","ParamC"} );
}});
        aBitOfLegacy.populate(mockSource);
        assertThat(aBitOfLegacy.output,not(EMPTY));
    }

.Netで

var mockSource = new Mock<ParameterSource>();
mockSource.Setup(src => src.GetParameters())
          .Returns(new []{"ParamA","ParamB","ParamC"});
于 2013-03-11T10:23:05.573 に答える