Просматривая мои статьи о DZone, можно легко заключить, что я фанат среды Spring Boot для разработки приложений RESTful. Этот же запрос может легко сделать вывод, что я фанат предоставления достаточного охвата модульных тестов для тех же самых API RESTful .
В недавнем проекте я понял, что использование @MockBean в моих модульных тестах сильно влияло на время выполнения этапа тестирования в нашем конвейере CI / CD .
В этой статье рассказывается об одном способе решения этой проблемы.
Вам также может понравиться: Модульное тестирование: хорошее, плохое и уродливое
@MockBean
Аннотация @MockBean добавит фиктивные объекты в контекст приложения Spring. В результате, смоделированный элемент заменит любой существующий бин того же типа в контексте приложения. Когда это происходит, Spring Boot понимает, что контекст приложения должен быть перезагружен как часть инициализации теста.
Хотя это может показаться не таким уж большим делом, особенно когда это обычно 10-15-секундный процесс, это может повлиять на время развертывания — когда нужно покрыть большую кодовую базу.
Простой пример
Рассмотрим этот простой пример класса обслуживания.
Джава
x
1
2
3
public class WidgetServiceImpl implements WidgetService {
4
private final AccountService accountService;
5
private final WidgetRepository widgetRepository;
6
7
/**
8
* {@inheritDoc}
9
*/
10
11
public List<Widget> getWidgetsByAccountId(Long accountId, String authId) throws AccountException {
12
Account account = accountService.getAccountById(accountId, authId);
13
return widgetRepository.getWidgetsByAccountId(accountId);
14
}
15
}
В приведенном выше примере используются инъекции на основе конструктора с помощью аннотации Lombok @RequiredArgsConstructor, поэтому в коде не существует общедоступного конструктора.
Для успешного выполнения метода getWidgetsByAccountId accountId и authId должны быть предоставлены AccountService для возврата действительной учетной записи. Этот шаг просто чтобы проиллюстрировать мою точку зрения в модульных тестах — когда сервис зависит от чего-то другого. По сути, бизнес-правило гласит, что WidgetService должен взаимодействовать с AccountService как часть процесса запроса.
Если AccountService не выбрасывает AccountException, WidgetRepository (интерфейс JPA) просто вернет List <Widget> для предоставленного accountId.
Фон модульного тестирования
В этом примере необходимо смоделировать и AccountService, и WidgetRepository, чтобы предоставить информацию для WidgetService … или тестируемой системы (SUT).
Я всегда был сторонником сохранения модульных тестов, ориентированных на SUT, и одобряю использование имитации вместо использования соединения с базой данных, которое может иметь или не всегда иметь данные, необходимые для правильной работы модульного теста.
В классе WidgetServiceTest метод Mockito when () используется для взаимодействия с фиктивными службами и предоставления имитированных результатов, предназначенных для тестирования SUT.
Некоторые примеры могут включать в себя:
- Предоставление ожидаемых данных учетной записи для обработки результатов.
- Предоставление ожидаемых данных хранилища виджетов для обработки результатов.
- Создание исключений для проверки срабатываний AccountException.
Посмеиваясь над введенными зависимостями, модульные тесты остаются сфокусированными на SUT.
Тестирование простого примера с использованием @MockBean
С Spring Boot очень возможно настроить WidgetServiceTest, используя @MockBean и @Autowired:
Джава
xxxxxxxxxx
1
public class WidgetServiceTest extends BaseServiceTest {
2
3
AccountService accountService;
4
5
WidgetRepository widgetRespository;
6
7
WidgetServiceImpl widgetService;
8
9
...
10
}
Каждый сервис, который использует @MockBean для AccountService, будет вызывать перезагрузку контекста приложения в Sprint Boot. В моем случае это добавило 12-секундную задержку перед выполнением первого теста в классе.
Обновление простого примера
В приведенном ниже примере один и тот же класс обслуживания может быть протестирован без использования @MockBean и перезагрузки контекста приложения:
Джава
xxxxxxxxxx
1
public class WidgetServiceTest extends BaseServiceTest {
2
private final AccountService accountService = Mockito.mock(AccountService.class);
3
private final WidgetRepository widgetRespository = Mockito.mock(WidgetRepository.class);
4
WidgetServiceImpl widgetService = new WidgetService(accountService, widgetRepository);
5
6
...
7
}
Заключение
Влияние на внесение этого изменения привело к решению проблем с процессом сборки, и командам времени ожидания пришлось терпеть, когда обновления стали доступны для API RESTful. Это привело к сокращению сроков выполнения работ, что положительно сказалось на производительности проекта.
Конечно, есть случаи, когда @ MockBean можно использовать и не оказать негативного влияния на процесс модульного тестирования. В сценарии, в котором существуют зависимости от других служб, которые, вероятно, используются другими службами, Spring Boot потребует перезагрузки контекста приложения перед началом испытаний. В этом случае ожидайте дополнительное время.
Хорошего дня!
Дальнейшее чтение
Лучшие практики для написания юнит-тестов
Руководство по модульному тестированию: что тестировать, а что не тестировать