Статьи

Модульное тестирование с использованием макетов и заглушек

В моей последней статье « Введение в модульное тестирование» я привел пример того, как можно написать модульный тест для простого старого объекта Java (POJO) с использованием JUnit . В этой статье основное внимание будет уделено макетам и объектам-заглушкам, поэтому для проверки тестируемой системы не требуется никаких внешних интеграций. В духе праздников я продолжу использовать набор предметов, вдохновленных конфетами.  

Использование фиктивных объектов

Википедия определяет фиктивные объекты  как «симулируемые объекты, которые имитируют поведение реальных объектов контролируемыми способами». Имитация объектов важна, поскольку они позволяют разработчику сосредоточиться на тестируемой системе (или коде) и не должны полагаться на внешний элемент — например, на базу данных или уровень персистентности.  

Рассмотрим пример ниже:

public class CandyService {
    private static final Double ZERO = new Double("0");
    private CandyRepository candyRepository;

    public CandyService(CandyRepository candyRepository) {
        this.candyRepository = candyRepository;
    }

    public Double getAverageCandySize() {
        Double returnValue = new Double("0");
        List<Candy> candyList = candyRepository.getAllCandy();

        if (candyList != null && candyList.size() > 0) {
            Double candySizeSum = ZERO;
            for (Candy thisCandy : candyList) {
                if (thisCandy.getSize() != null) {
                    candySizeSum += thisCandy.getSize();
                }
            }

            if (!candySizeSum.equals(ZERO)) {
                returnValue = candySizeSum / candyList.size();
            }
        }

        return returnValue;
    }
}

Класс CandyService имеет единственный метод getAverageCandySize (), который возвращает средний размер элементов конфет в CandyRepository. Модуль, который будет написан, должен проверить логику в методе getAverageCandySize (). Однако, чтобы протестировать метод, ожидается, что список объектов Candy будет возвращен из класса CandyRepository.  

Используя Mockito , в качестве примера фальшивого фреймворка, можно избежать необходимости полагаться на реальный CandyRepository и симулировать результаты, чтобы облегчить тестирование. В этом примере простой метод setup () создается в классе CandyServiceTest:

@RunWith(MockitoJUnitRunner.class)
public class CandyServiceTest {
    private CandyService sut;

    @Before
    public void setup() throws Exception {
        CandyRepository mockCandyRepository = mock(CandyRepository.class);

        sut = new CandyService(mockCandyRepository);

        when(mockCandyRepository.getAllCandy()).thenReturn(createCandyList());
    }

    private List<Candy> createCandyList() {
        List<Candy> candyList = new ArrayList<Candy>();
        Candy candy = new Candy(new Integer("1"), new String("Good & Plenty"), new Integer("51"));
        candyList.add(candy);

        candy = new Candy(new Integer("2"), new String("Mike & Ike"), new Integer("49"));
        candyList.add(candy);

        return candyList;
    }
}

В методе setup () создается фиктивное хранилище Candy (называемое mockCandyRepository) для использования во время тестов. Mockito использует метод when () для управления тем, какие результаты будут найдены при вызове метода getAllCandy (). В этом случае процесс будет включать метод thenReturn () и вызывать закрытый метод, который будет возвращать простой список объектов Candy.

На этом этапе модульный тест можно добавить:

    @Test
    public void standardCandyServiceAverageTest() {
        Double result = sut.getAverageCandySize();
        assertEquals(result, new Double("50"));
    }

В тесте мы просто вызываем метод getAverageCandySize (), который будет использовать логику метода setup () для выделения двух объектов Candy:

  • Объект Candy №1, называемый Good & Plenty, имеет размер 51.

  • Объект Candy # 2, названный Mike & Ike, имеет размер 49.

При сложении этих двух значений получается 100, что при делении на два дает в среднем 50. При запуске класса CandyServiceTest тест проходит, как и ожидалось:

Работа с покрытием Clover в IntelliJ показывает, что покрытие исходного класса на 100%:

В результате использования среды моделирования, такой как Mockito, мы смогли проверить логику в CandyService без необходимости полагаться на точку интеграции — например, на базу данных или уровень персистентности. Это важно, потому что данные в базе данных могут измениться или база данных может быть недоступна, что приведет к неожиданному поведению при выполнении модульного теста.

Объекты-заглушки

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

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

Вывод

Koskis Kapelonis из ZeroTurnaround предлагает отличную оценку между модульными тестами и интеграционными тестами в своей статье «Как правильно использовать интеграционные тесты в процессе сборки ». Перефразировать:

Если тестируемая система …

  1. использует базу данных
  2. использует сеть
  3. использует внешнюю систему (например, очередь или почтовый сервер)
  4. читает / пишет файлы или выполняет другой ввод / вывод

… Тогда это интеграционный тест, а не модульный тест .

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

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

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