1. 概要

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

これはTDDの紹介ではないので、それが何を意味するのか、そしてそれをより良くするための持続的な関心について、すでにいくつかの基本的な考えを持っていると想定しています。

簡単に言えば、 TDDは設計ツールであり、テストを使用して実装を推進できます。

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

2. 入門

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

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

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

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

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

3. TDDサイクル

TDDを使用して実装を開発するということは、最初にテストケースを作成する必要があることを意味します。これにより、実装の要件を定義します。 のみ、実装コードを作成または修正して、これらのテストに合格させます。

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

  1. ライティングテスト–はテストの形で要件を定義します
  2. 機能の実装– は、コードの優雅さにあまり焦点を当てることなく、テストに合格します
  3. リファクタリング– コードを改善して、テストに合格しながらも読みやすく、保守しやすくします。

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

4. isEmptyメソッド

isEmpty メソッドは、おそらくListインターフェースで定義されている最も簡単なメソッドです。 これが私たちの最初の実装です:

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

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

4.1. 最初のサイクル

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

@Test
public void givenEmptyList_whenIsEmpty_thenTrueIsReturned() {
    List<Object> 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<Object> 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メソッド

これは、 size メソッドの最初の実装であり、CustomListクラスをコンパイルできるようにします。

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

5.1. 最初のサイクル

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

@Test
public void givenListWithAnElement_whenSize_thenOneIsReturned() {
    List<Object> 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. 最初のサイクル

このメソッドの最初のテストを見てみましょう。これは、リスト内の単一の要素の値を検証します。

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

    assertEquals("baeldung", 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<Object> 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<Object> list = new CustomList<>();
    list.add("baeldung");
    list.add(".com");
    Object element1 = list.get(0);
    Object element2 = list.get(1);

    assertEquals("baeldung", 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を使用すると、テストカバレッジを非常に高いレベルに保ちながら、要件を段階的に実装できます。 また、実装はテストに合格するために作成されたため、テスト可能であることが保証されています。

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

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