Как TDD реализация списка в Java

Как TDD Реализация Списка в Java

 

1. обзор

В этом руководстве мы рассмотрим индивидуальную реализацию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, поэтому он должен содержать реализации для всех методов, объявленных в этом интерфейсе.

Для начала мы можем просто предоставить пустые тела для этих методов. Если у метода есть тип возврата, мы можем вернуть произвольное значение этого типа, напримерnull дляObject илиfalse дляboolean.

Для краткости мы опустим необязательные методы вместе с некоторыми обязательными, которые используются нечасто.

3. Циклы TDD

Разработка нашей реализации с помощью TDD означает, что нам нужноcreate test cases first, тем самым определяя требования для нашей реализации. Толькоthen we’ll create or fix the implementation code, чтобы эти тесты прошли.

В очень упрощенном виде, три основных шага в каждом цикле:

  1. Writing tests – определяют требования в виде тестов

  2. Implementing features – позволяет пройти тесты, не уделяя слишком много внимания элегантности кода

  3. Refactoring – улучшают код, чтобы его было легче читать и поддерживать при прохождении тестов

Мы рассмотрим эти циклы TDD для некоторых методов интерфейсаList, начиная с самых простых.

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. Второй цикл

Чтобы подтвердить, что методisEmpty возвращаетfalse, когда список не пуст, нам нужно добавить хотя бы один элемент:

@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

Это наша начальная реализация методаsize, позволяющая компилировать классCustomList:

@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. Первый цикл

Давайте посмотрим на первый тест этого метода, который проверяет значение одного элемента в списке:

@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 должен извлечь элемент из массиваinternal в указанном месте с помощью параметраindex:

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

7. Методadd

Это методadd, который мы создали в разделе 4:

@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 еще не охватывает все случаи. Если мы добавим второй элемент в список, существующий элемент будет потерян.

7.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.