Javaでリスト実装をTDDする方法

Javaでリスト実装をTDDする方法

 

1. 概要

このチュートリアルでは、テスト駆動開発(TDD)プロセスを使用したカスタムList実装について説明します。

これはTDDの紹介ではないため、TDDの意味についての基本的な考え方と、それを改善するための継続的な関心をすでに持っていることを前提としています。

簡単に言えば、TDD is a design tool, enabling us to drive our implementation with the help of testsです。

簡単な免責事項–ここでは効率的な実装の作成に焦点を当てていません–TDDプラクティスを表示するための言い訳として使用するだけです。

2. 入門

まず、クラスのスケルトンを定義しましょう。

public class CustomList implements List {
    private Object[] internal = {};
    // empty implementation methods
}

CustomListクラスはListインターフェースを実装するため、そのインターフェースで宣言されたすべてのメソッドの実装が含まれている必要があります。

開始するには、これらのメソッドに空のボディを提供するだけです。 メソッドに戻り値の型がある場合、Objectの場合はnullbooleanの場合はfalseなど、その型の任意の値を返すことができます。

簡潔にするために、オプションのメソッドと、あまり使用されない必須のメソッドを省略します。

3. TDDサイクル

TDDを使用して実装を開発するということは、create test cases firstを実行する必要があることを意味し、それによって実装の要件を定義します。 これらのテストに合格するには、then we’ll create or fix the implementation codeのみが必要です。

非常に簡単な方法で、各サイクルの3つの主要なステップは次のとおりです。

  1. Writing tests –はテストの形で要件を定義する。

  2. Implementing features –は、コードの優雅さにあまり焦点を当てることなく、テストに合格します。

  3. Refactoring –はコードを改善して、テストに合格しながらも読みやすく、保守しやすくします。

Listインターフェースのいくつかのメソッドについて、最も単純なものから始めて、これらのTDDサイクルを実行します。

4. isEmptyメソッド

isEmptyメソッドは、おそらくListインターフェイスで定義されている最も簡単なメソッドです。 開始時の実装は次のとおりです。

@Override
public boolean isEmpty() {
    return false;
}

この初期メソッド定義はコンパイルするのに十分です。 このメソッドの本体は、より多くのテストが追加されたときに改善するために「強制」されます。

4.1. 最初のサイクル

リストに要素が含まれていない場合にisEmptyメソッドがtrueを返すことを確認する最初のテストケースを書いてみましょう。

@Test
public void givenEmptyList_whenIsEmpty_thenTrueIsReturned() {
    List list = new CustomList<>();

    assertTrue(list.isEmpty());
}


isEmptyメソッドは常にfalseを返すため、指定されたテストは失敗します。 戻り値を反転するだけでパスできます:

@Override
public boolean isEmpty() {
    return true;
}

4.2. 2番目のサイクル

リストが空でないときにisEmptyメソッドがfalseを返すことを確認するには、少なくとも1つの要素を追加する必要があります。

@Test
public void givenNonEmptyList_whenIsEmpty_thenFalseIsReturned() {
    List list = new CustomList<>();
    list.add(null);

    assertFalse(list.isEmpty());
}


addメソッドの実装が必要になりました。 開始するaddメソッドは次のとおりです。

@Override
public boolean add(E element) {
    return false;
}

リストの内部データ構造に変更が加えられていないため、このメソッドの実装は機能しません。 追加した要素を保存するように更新しましょう。

@Override
public boolean add(E element) {
    internal = new Object[] { element };
    return false;
}

isEmptyメソッドが拡張されていないため、テストはまだ失敗します。 そうしよう:

@Override
public boolean isEmpty() {
    if (internal.length != 0) {
        return false;
    } else {
        return true;
    }
}

この時点で、空でないテストに合格します。

4.3. リファクタリング

これまで見てきた両方のテストケースは合格ですが、isEmptyメソッドのコードはより洗練されている可能性があります。

リファクタリングしましょう:

@Override
public boolean isEmpty() {
    return internal.length == 0;
}

テストに合格したことがわかるので、isEmptyメソッドの実装はこれで完了です。

5. sizeメソッド

これは、CustomListクラスのコンパイルを可能にするsizeメソッドの最初の実装です。

@Override
public int size() {
    return 0;
}

5.1. 最初のサイクル

既存のaddメソッドを使用して、sizeメソッドの最初のテストを作成し、単一の要素を持つリストのサイズが1であることを確認できます。

@Test
public void givenListWithAnElement_whenSize_thenOneIsReturned() {
    List list = new CustomList<>();
    list.add(null);

    assertEquals(1, list.size());
}


sizeメソッドが0を返しているため、テストは失敗します。 新しい実装で合格させましょう。

@Override
public int size() {
    if (isEmpty()) {
        return 0;
    } else {
        return internal.length;
    }
}

5.2. リファクタリング

sizeメソッドをリファクタリングして、よりエレガントにすることができます。

@Override
public int size() {
    return internal.length;
}

これで、このメソッドの実装は完了しました。

6. getメソッド

getの最初の実装は次のとおりです。

@Override
public E get(int index) {
    return null;
}

6.1. 最初のサイクル

このメソッドの最初のテストを見てみましょう。このテストでは、リスト内の1つの要素の値を確認します。

@Test
public void givenListWithAnElement_whenGet_thenThatElementIsReturned() {
    List list = new CustomList<>();
    list.add("example");
    Object element = list.get(0);

    assertEquals("example", element);
}


テストは、getメソッドの次の実装で合格します。

@Override
public E get(int index) {
    return (E) internal[0];
}

6.2. 改善

通常、getメソッドをさらに改善する前に、テストを追加します。 これらのテストでは、適切なアサーションを実装するために、Listインターフェイスの他のメソッドが必要になります。

ただし、これらの他のメソッドはまだ十分に成熟していないため、TDDサイクルを中断し、getメソッドの完全な実装を作成します。これは実際にはそれほど難しくありません。

getは、indexパラメータを使用して、指定された場所にあるinternal配列から要素を抽出する必要があることは容易に想像できます。

@Override
public E get(int index) {
    return (E) internal[index];
}

7. addメソッド

これは、セクション4で作成したaddメソッドです。

@Override
public boolean add(E element) {
    internal = new Object[] { element };
    return false;
}

7.1. 最初のサイクル

以下は、addの戻り値を検証する簡単なテストです。

@Test
public void givenEmptyList_whenElementIsAdded_thenGetReturnsThatElement() {
    List list = new CustomList<>();
    boolean succeeded = list.add(null);

    assertTrue(succeeded);
}


テストに合格するには、addメソッドを変更してtrueを返す必要があります。

@Override
public boolean add(E element) {
    internal = new Object[] { element };
    return true;
}

テストは合格ですが、addメソッドはまだすべてのケースをカバーしているわけではありません。 リストに2番目の要素を追加すると、既存の要素は失われます。

7.2. 2番目のサイクル

リストに複数の要素を含めることができるという要件を追加する別のテストを次に示します。

@Test
public void givenListWithAnElement_whenAnotherIsAdded_thenGetReturnsBoth() {
    List list = new CustomList<>();
    list.add("example");
    list.add(".com");
    Object element1 = list.get(0);
    Object element2 = list.get(1);

    assertEquals("example", element1);
    assertEquals(".com", element2);
}


現在の形式のaddメソッドでは複数の要素を追加できないため、テストは失敗します。

実装コードを変更しましょう:

@Override
public boolean add(E element) {
    Object[] temp = Arrays.copyOf(internal, internal.length + 1);
    temp[internal.length] = element;
    internal = temp;
    return true;
}

実装は十分にエレガントであるため、リファクタリングする必要はありません。

8. 結論

このチュートリアルでは、テスト駆動開発プロセスを経て、カスタムList実装の一部を作成しました。 TDDを使用すると、テストカバレッジを非常に高いレベルに保ちながら、要件を段階的に実装できます。 また、テストに合格するために作成されたため、実装はテスト可能であることが保証されています。

この記事で作成したカスタムクラスは、デモンストレーションの目的でのみ使用されるものであり、実際のプロジェクトでは採用しないでください。

簡潔にするために省略されたテストおよび実装メソッドを含む、このチュートリアルの完全なソースコードは、over on GitHubにあります。