Рубрики
Без рубрики

Как ДОБАВИТЬ реализацию списка в Java

Изучите разработку на основе тестов при реализации реализации списка на Java.

Автор оригинала: Nguyen Nam Thai.

1. Обзор

В этом уроке мы рассмотрим пользовательскую реализацию List с использованием процесса разработки на основе тестирования (TDD).

Это не введение в TDD, поэтому мы предполагаем, что у вас уже есть некоторое базовое представление о том, что это значит, и устойчивый интерес к тому, чтобы стать лучше в этом.

Проще говоря, TDD-это инструмент проектирования, позволяющий нам управлять нашей реализацией с помощью тестов .

Краткое заявление об отказе от ответственности – мы не фокусируемся на создании эффективной реализации здесь – просто используем его в качестве предлога для демонстрации практики TDD.

2. Начало работы

Во-первых, давайте определим скелет для нашего класса:

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

Класс Custom List реализует интерфейс List , следовательно, он должен содержать реализации для всех методов, объявленных в этом интерфейсе.

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

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

3. Циклы TDD

Разработка нашей реализации с помощью TDD означает , что сначала нам нужно создать тестовые случаи , тем самым определив требования к нашей реализации. Только затем мы создадим или исправим код реализации , чтобы эти тесты прошли.

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

  1. Написание тестов – определение требований в виде тестов
  2. Реализация функций – сделайте так, чтобы тесты проходили, не слишком фокусируясь на элегантности кода
  3. Рефакторинг – улучшите код, чтобы его было легче читать и поддерживать, все еще проходя тесты

Мы рассмотрим эти циклы 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 , позволяющая компилировать Пользовательский список класс:

@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("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 :

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

7. Метод добавления

Это метод 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("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 .