Когда использовать фиктивные объекты в модульных тестах - PullRequest
3 голосов
/ 10 мая 2019

Я знаю, что есть много вопросов о насмешках и тестировании, но я не нашел ни одного, который бы помог мне идеально, поэтому у меня все еще есть проблемы с пониманием следующего:

Пожалуйста, исправьте меня, если я ошибся, но, насколько я вижу, модульные тесты используются для проверки бизнес-логики одного отдельного класса в отдельности, и если есть какие-либо объекты, необходимые извне, они будут подвергнуты насмешке. Например, если у меня есть система управления гражданами простого города, которая добавляет граждан в список и возвращает гражданина по имени (Предположение: граждане состоят только из нескольких основных персональных данных), например:

public class ProcessClass {

    ArrayList<Citizen> citizenList = new ArrayList<Citizen>();

    public void addCitizen(Citizen citizen) {
        citizenList.add(citizen);
    }

    public Citizen getByName(String name) {
        for (Citizen c : citizenList) {
            if (c.getName().equals(name)) {
                return c;
            }
        }
        return null;
    }

}

Если теперь я хочу провести модульное тестирование своего ProcessClass, считаю ли я Citizen внешней функцией, которую необходимо смоделировать, или я просто создаю Citizen для целей тестирования? Если они проверяются, как бы я протестировал метод, чтобы получить объект по его имени, так как этот объект не содержит параметров?

Ответы [ 5 ]

3 голосов
/ 10 мая 2019

Когда вы пишете новый код (вместе с новыми модульными тестами) или реорганизуете существующий код, вы хотите иметь возможность запускать модульные тесты снова и снова , чтобы быть достаточно уверенными в том, что существующая функциональность не быласломана.Следовательно, модульные тесты должны быть стабильными и быстрыми .

. Предположим, что класс для тестирования зависит от какого-то внешнего ресурса, такого как база данных.Вы вносите изменения в код, и модульные тесты неожиданно дают сбой.Сбои юнит-тестов произошли из-за ошибки, которую вы только что внесли, или из-за того, что внешний ресурс недоступен?Нет гарантии, что внешний ресурс всегда будет доступен, поэтому юнит-тесты нестабильны.Смоделируйте внешний ресурс.

Кроме того, подключение к внешнему ресурсу может занять слишком много времени.Когда у вас в конце концов появятся тысячи тестов, которые подключаются к различным внешним ресурсам, миллисекунды для подключения к внешнему ресурсу накапливаются, что замедляет работу.Смоделируйте внешний ресурс.

Теперь добавьте конвейер CI / CD.Во время сборки модульные тесты не пройдены.Внешний ресурс не работает или ваш код изменил что-то?Возможно, у сервера сборки нет доступа к внешнему ресурсу?Дразнить внешний ресурс.

2 голосов
/ 12 мая 2019

Чтобы ответить на первую часть вашего вопроса

Если сейчас я хочу провести модульное тестирование моего ProcessClass, я рассматриваю Citizen как внешнюю функцию, которую необходимо смоделировать, или просто создаюГражданин для целей тестирования?

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

  • Вы не можете легко заставить зависимый компонент (DOC) вести себя так, как предназначено для ваших тестов.
  • Вызывает ли DOC какое-либо недерминистское поведение(дата / время, случайность, сетевые подключения)?
  • Тестовая настройка слишком сложна и / или требует интенсивного обслуживания (например, необходимость во внешних файлах)
  • Исходный DOC создает проблемы с переносимостью для вашегокод теста.
  • Вызывает ли использование оригинального DOC неприемлемо длительное время сборки / выполнения?
  • Имеет ли проблемы со стабильностью (зрелостью) DOC, которые делают тесты ненадежными, или, что еще хуже, DOC недаже доступны?В вашем случае вам необходимо решить, вызовет ли простое использование Citizen какую-либо из вышеупомянутых проблем.Если так, то, скорее всего, лучше издеваться над этим, иначе лучше не издеваться.
2 голосов
/ 10 мая 2019

В вашем конкретном примере нет, вам не нужно что-либо издеваться.

Давайте сосредоточимся на том, что вы будете тестировать:

  1. тест, в котором вы добавляете и извлекаете одного гражданина
  2. добавьте 2 граждан, извлеките один
  3. пропустите null вместо гражданина и убедитесь, что ваш код не нарушается.
  4. добавьте двух граждан с одинаковым именем, что бывы ожидаете, что произойдет тогда?
  5. добавьте гражданина без имени.
  6. добавьте гражданина с пустым именем

и т. д.

ВыУже можно увидеть ряд различных тестов, которые вы можете написать.

Чтобы сделать его более интересным, вы можете добавить в свой класс некоторый код, который предоставляет доступную только для чтения версию вашего списка CitizenList, а затем проверить, что в вашем списке содержатся именно нужные вещи.

Итак, в вашем сценарии вам не нужно что-либо издеваться, поскольку у вас нет внешних зависимостей от другой системы какого-либо типа.Гражданин, кажется, простой модельный класс, не более того.

2 голосов
/ 10 мая 2019

Чаще всего насмешка используется для замены реальных звонков, которые трудно дублировать при тестировании.Например, предположим, что ProcessClass выполняет вызов REST для получения информации о гражданине.Для простого модульного теста было бы трудно дублировать этот вызов REST.Тем не менее, вы можете «смоделировать» RestTemplate и продиктовать различные типы возвратов, чтобы гарантировать, что ваш код будет обрабатывать 200, 403 и т. Д. Кроме того, вы можете изменить тип информации, а затем также протестировать свой код, чтобы убедиться, что неверные данные обрабатываются, как отсутствующая или нулевая информация.

В вашем случае вы можете создать Citizen, а затем проверить, что Citizen является объектом в списке или что getByName возвращает правильный объект.Так что насмешка в этом примере не нужна.

1 голос
/ 10 мая 2019

Если имитировать, как бы я протестировал метод, чтобы получить объект по его имени, поскольку фиктивный объект не содержит параметров?

Вы можете посмеяться над вызовомgetName, используя mockito, например:

Citizen citizen = mock(Citizen.class);
when(citizen.getName()).thenReturn("Bob");

Вот пример теста для вашего метода

ProcessClass processClass = new ProcessClass();

Citizen citizen1 = mock(Citizen.class);
Citizen citizen2 = mock(Citizen.class);
Citizen citizen3 = mock(Citizen.class);

@Test
public void getByName_shouldReturnCorrectCitizen_whenPresentInList() {
    when(citizen1.getName()).thenReturn("Bob");
    when(citizen2.getName()).thenReturn("Alice");
    when(citizen3.getName()).thenReturn("John");

    processClass.addCitizen(citizen1);
    processClass.addCitizen(citizen2);
    processClass.addCitizen(citizen3);

    Assert.assertEquals(citizen2, processClass.getByName("Alice"));
}

@Test
public void getByName_shouldReturnNull_whenNotPresentInList() {
    when(citizen1.getName()).thenReturn("Bob");

    processClass.addCitizen(citizen1);

    Assert.assertNull(processClass.getByName("Ben"));
}

Примечание:

Я бы порекомендовал издеваться.Допустим, вы пишете 100 тестов, в которых вы создаете экземпляр класса Citizen таким образом

Citizen c = new Citizen();

, и через несколько месяцев ваш конструктор изменится на аргумент, который представляет собой сам объект, класс City дляпример.Теперь вам нужно вернуться и изменить все эти тесты и написать:

City city = new City("Paris");
Citizen c = new Citizen(city);

Если вы начали издеваться над Citizen, вам не нужно это делать.

Теперь, как этоявляется POJO, и его конструктор метода getName может не измениться, но без насмешек все равно должно быть в порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...