Статьи

Введение в модульное тестирование

Я был поклонником написания юнит-тестов в течение достаточно долгого времени. Один из моих любимых примеров — когда я работал с командой разработчиков, переписывая процесс учета. Руководитель команды разбил нам работу, чтобы мы могли целенаправленно завершить работу. Проблема была в том, что каждому из нас требовалась работа от других разработчиков … работа, которая еще не была завершена.

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

В этой короткой серии статей я планирую представить следующее:

  • Простое введение в юнит-тесты (эта статья)

  • Введение в макет и заглушки объектов

  • Различение юнит-тестов и интеграционных тестов

  • Заглядывая вперед

Поскольку у меня большой опыт работы с Java , я планирую сосредоточиться на языке программирования Java и инфраструктуре тестирования JUnit . Однако представленные концепции должны хорошо переводиться на другие объектно-ориентированные языки.

Простой пример — Candy Class

Для простоты рассмотрим следующий класс Candy:

public class Candy {
    private static Double OUNCE_CONVERSION = new Double("0.035274");
    private Integer id;
    private String name;
    private Integer size;

    public Candy() {
    }

    public Candy(Integer id, String name, Integer size) {
        this.id = id;
        this.name = name;
        this.size = size;
    }

    // getters and setters go here

    public Double getSizeInOunces() {
        if (this.size != null) {
            return new Double(this.size * OUNCE_CONVERSION);
        } else {
            return new Double("0");
        }
    }
}

Объект Candy включает в себя идентификатор, имя и размер (который хранится в граммах). Вы заметите, что в этом классе есть вспомогательный метод. Хотя это не является обычной практикой, я добавил вспомогательный метод getSizeInOunces () для преобразования объекта размера из граммов в унции. Я также добавил статическую переменную Double, используемую для размещения множителя конверсии. Опять же, это просто для простоты.

Простой пример — класс CandyTest

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

Используя JUnit, я установил некоторые статические переменные для размещения данных, используемых для тестирования объекта:

    public static final Integer ID_ONE = new Integer("1");
    public static final String NAME_GOOD_AND_PLENTY = new String("Good & Plenty");
    public static final Integer SIZE_GRAMS_51 = new Integer("51");
    public static final Double SIZE_GRAMS_51_IN_OUNCES = new Double("1.798974");
    public static final Double SIZE_GRAMS_NULL_IN_OUNCES = new Double("0");
    public static final Double SIZE_GRAMS_51_IN_OUNCES_INCORRECT = new Double("1.967527");

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

Первый элемент, который мне нравится тестировать, — это тест «счастливый путь», где все работает как положено:

    @Test
    public void standardTest() {
        Candy candy = new Candy(ID_ONE, NAME_GOOD_AND_PLENTY, SIZE_GRAMS_51);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSize().equals(SIZE_GRAMS_51));
        assertTrue(candy.getSizeInOunces().equals(SIZE_GRAMS_51_IN_OUNCES));
    }

Тест устанавливает новый объект Candy с помощью пользовательского конструктора, а затем проверяет соответствие значений. Наконец, метод getSizeInOunces () проверяется на точность.  

Далее создаются еще два теста. Первый из них устанавливает размер конфеты на ноль, а второй гарантирует, что тест подтвердит ложь, когда предлагается неправильное преобразование грамма в унцию:

    @Test
    public void nullSizeTest() {
        Candy candy = new Candy();
        candy.setId(ID_ONE);
        candy.setName(NAME_GOOD_AND_PLENTY);
        candy.setSize(null);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSizeInOunces().equals(SIZE_GRAMS_NULL_IN_OUNCES));
    }

    @Test
    public void incorrectConversionTest() {
        Candy candy = new Candy(ID_ONE, NAME_GOOD_AND_PLENTY, SIZE_GRAMS_51);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSize().equals(SIZE_GRAMS_51));
        assertFalse(candy.getSizeInOunces().equals(SIZE_GRAMS_51_IN_OUNCES_INCORRECT));
    }

Вы заметите, что метод nullSizeTest () использует методы конструктора и сеттера по умолчанию. Это было выбрано для достижения 100% покрытия кода.

Полный тестовый класс указан ниже:

public class CandyTest {
    public static final Integer ID_ONE = new Integer("1");
    public static final String NAME_GOOD_AND_PLENTY = new String("Good & Plenty");
    public static final Integer SIZE_GRAMS_51 = new Integer("51");
    public static final Double SIZE_GRAMS_51_IN_OUNCES = new Double("1.798974");
    public static final Double SIZE_GRAMS_NULL_IN_OUNCES = new Double("0");
    public static final Double SIZE_GRAMS_51_IN_OUNCES_INCORRECT = new Double("1.967527");

    @Test
    public void standardTest() {
        Candy candy = new Candy(ID_ONE, NAME_GOOD_AND_PLENTY, SIZE_GRAMS_51);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSize().equals(SIZE_GRAMS_51));
        assertTrue(candy.getSizeInOunces().equals(SIZE_GRAMS_51_IN_OUNCES));
    }

    @Test
    public void nullSizeTest() {
        Candy candy = new Candy();
        candy.setId(ID_ONE);
        candy.setName(NAME_GOOD_AND_PLENTY);
        candy.setSize(null);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSizeInOunces().equals(SIZE_GRAMS_NULL_IN_OUNCES));
    }

    @Test
    public void incorrectConversionTest() {
        Candy candy = new Candy(ID_ONE, NAME_GOOD_AND_PLENTY, SIZE_GRAMS_51);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSize().equals(SIZE_GRAMS_51));
        assertFalse(candy.getSizeInOunces().equals(SIZE_GRAMS_51_IN_OUNCES_INCORRECT));
    }
}

Простой пример — результаты CandyTest

Выполнение тестов с Clover Coverage в IntelliJ дает следующие результаты:

Все три теста прошли с зеленым значком ОК. Просмотр оригинального класса Candy также показывает 100% охват:

Вывод

В этом простом примере был создан новый класс, который включал простой расчет. Используя JUnit, был создан тестовый класс для проверки того, что класс Candy функционирует должным образом.  

В следующей статье я представлю класс обслуживания Candy, который потребует использования фиктивных объектов для проверки логики обслуживания без необходимости полагаться на какие-либо внешние объекты или возможности подключения.

Хорошего дня!