Разработчики, как правило, относятся к модульному тестированию с любовью и ненавистью. Нам всем нравится этот дофамин, когда мы видим все зеленые галочки ✔️ из набора тестов, но не всем нравится это писать.

Модульное тестирование является критическим аспектом разработки программного обеспечения, поскольку оно дает следующие преимущества процессу разработки:

  1. Повышает уверенность в надежности вашей кодовой базы.
  2. Обеспечивает инверсию шаблона управления / внедрения зависимостей
  3. Подталкивает разработчика к написанию хорошего кода (плохой код часто бывает сложно протестировать и интерпретировать)
  4. Защищает ваши требования и предотвращает их случайное нарушение другими.
Table of Contents
 — Dependencies Setup
 — Sample Project
 — Test Naming Convention
 — Writing Unit Test
 — Mocking Service Object
 — Argument Captor for Mocked Object
 — Mocking Public Static Method
 — Testing Private Method (Reflection)
 — Review Test Coverage

Настройка зависимостей

Для начала мы будем использовать mockito-core, которого достаточно для имитации или частичного имитации. Позже будут примеры, когда мы переключимся на использование mokito-inline.

Обратите внимание, что для этого примера проекта нам понадобится как минимум Java 8.

Образец проекта

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

Класс MessageService - это основная служба, которая предоставляет 2 метода для отправки электронной почты. В целях безопасности служба сообщений может отправлять электронную почту с шифрованием или без него. Метод, обеспечивающий шифрование, будет использовать другой метод шифрования, предоставляемый EncryptionUtils, в зависимости от входной длины сообщения:

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

EncryptionUtils предоставляет статические методы для шифрования входных сообщений:

EmailEntity - это класс-оболочка, который обертывает сообщение перед его передачей в экземпляр EmailService. В этом примере реализация класса-оболочки очень проста; в реальном сценарии очень часто можно увидеть более сложные классы с аналогичным вариантом использования:

Соглашение об именовании тестов

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



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

Написание модульного теста

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

  • идея Mock и Argument Captor
  • утверждение результатов
  • проверка вызовов методов

Класс имеет аннотацию ExtendWith и указанный MockitoExtension.class - он предназначен для незаметной инициализации Mockito. В классе есть еще 3 аннотации -

  • Mock: указывает, что объект будет использоваться для подстановки результатов или проверки вызовов методов.
  • Captor: это означает, что объект ArgumentCaptor используется для захвата значений аргументов, используемых имитируемым объектом (в данном случае - службой электронной почты).
  • BeforeEach: это означает, что метод init () будет вызываться каждый раз перед запуском каждого теста. Есть еще несколько аннотаций жизненного цикла JUnit5 для вашей справки - BeforeAll, AfterEach и AfterAll.

Мокинг сервисного объекта

Мокинг - это, по сути, создание контролируемой среды для имитации ответа вашего имитируемого объекта; вы не инициализируете и не вызываете фактический объект. Используя аннотацию Mock, мы имитируем поведение EmailService.

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

  • учитывая, что служба электронной почты активна
  • когда мы вызываем метод sendEmailMsg
  • затем результат вызова метода должен быть истинным
  • мы также проверяем, что пока вызывается метод, метод отправки из службы электронной почты также должен вызываться один раз

Итак, если мы не имитировали почтовую службу, метод isServiceActive () вернет false:

public boolean isServiceActive() {
    return false;
}

Используя Mockito.when и thenReturn, мы взломали поведение поддельной службы электронной почты. Более того, мы видим, как имитируемый emailService взаимодействует на протяжении всего вызова метода, что позволяет нам проверить вызов метода для метода send () в этом случае.

Captor аргумента для фиктивного объекта

Как мы проверяем, что emailEntity, передаваемый emailService.send () через sendEmailMsg (), отправляет ожидаемое сообщение?

В этом тесте вместо того, чтобы ожидать, что метод отправки будет каким-либо классом EmailEntity, мы меняем его местами на наш объект захвата аргументов. Таким образом, после проверки вызова метода мы можем вызвать метод getValue () для получения EmailEntity, фактически переданного методу. Поэтому мы можем выполнить дальнейшую оценку, чтобы убедиться, что объект соответствует ожиданиям:

А как насчет метода sendEncryptedEmailMsg ()? Давайте проверим, что объект emailEntity, передаваемый в emailService.send (), действительно отправляет зашифрованное сообщение:

Мокинг общедоступного статического метода

Еще раз оглядываясь на метод sendEncryptedEmailMsg ():

public boolean sendEncryptedEmailMsg(String message) {
    if (emailService.isServiceActive()) {
        String encryptedMsg;
        if (message.length() < 15) {
            encryptedMsg = EncryptionUtils.simpleEncrypt(message);
        } else {
            encryptedMsg = EncryptionUtils.notSoSimpleEncrypt(message);
        }

        EmailEntity emailEntity = new EmailEntity();
        emailEntity.setMessage(encryptedMsg);
        emailService.send(emailEntity);
        return true;
    } else {
        return false;
    }
}

В зависимости от длины сообщения для его обработки будут использоваться разные шифрования. Но в настоящее время нет возможности издеваться над статическим методом, верно?

Mockito-inline предоставляет нам реализацию для имитации общедоступного статического метода.

Теперь давайте сделаем еще один шаг вперед с зависимостью mockito-inline, которая предоставляет класс MockedStatic для имитации любого общедоступного статического метода. Поддерживается только после версии 3.4.0. В приведенных ниже примерах мы, наконец, можем имитировать класс EncryptionUtils и все его методы:

Тестирование частного метода (отражение)

Хотя тестирование частного метода не рекомендуется (вам, вероятно, следует провести рефакторинг своего кода, если вам действительно нужно его протестировать), все же существуют методы, которые позволяют нам это сделать.

Чтобы протестировать частный метод, мы можем использовать Java Reflection API. Он позволяет изменять поведение методов и объектов во время выполнения. Чтобы продемонстрировать это, давайте теперь переключимся на класс EmailServiceTest. Вот базовая настройка MessageServiceTest:

Следующий тест использует Reflection API для изменения средства доступа во время выполнения и позволяет нам вызывать ранее закрытый метод:

Обратите внимание, что Reflection будет иметь накладные расходы при использовании, поэтому мы должны стараться не использовать его в коде нашего приложения.

Вы можете узнать больше о Java Reflection API здесь:



Обзор покрытия тестами

Покрытие тестирования обеспечивает измеримые, но не исчерпывающие показатели вашей кодовой базы:

Эти показатели могут быть включены в непрерывную интеграцию (CI), которая помечает проблемы до или во время процесса интеграции.

Издевательский частный метод…?

Если вы достигли точки, когда вам нужно имитировать частный метод, это, вероятно, означает, что вам следует подумать о рефакторинге кода. Не забывайте всегда ссылаться на ТВЕРДЫЙ принцип. Если по какой-то причине вам не разрешено изменять средство доступа или рефакторинг кода, но вы все еще хотите имитировать частный метод и т. Д., Обратите внимание на PowerMock (настоятельно не рекомендуется).

Я загрузил все примеры в свой репозиторий, если вам нужны ссылки:



Сообщите мне, есть ли в статье какие-либо ошибки, и я буду обновлять ее, когда это возможно.

Спасибо, что прочитали мою первую статью!