Angular и Spring Boot — использование Serenity BDD для интеграционного тестирования
<h2>Вступление</h2><br><p>Создание современных веб-приложений с использованием комбинации Angular и Spring Boot очень популярно как среди крупных, так и среди малых предприятий. Angular предоставляет
Создание современных веб-приложений с использованием комбинации Angular и Spring Boot очень популярно как среди крупных, так и среди малых предприятий. Angular предоставляет все необходимые инструменты для создания надежного, быстрого и масштабируемого внешнего интерфейса, в то время как Spring Boot делает то же самое для внутреннего интерфейса без хлопот с настройкой и обслуживанием сервера веб-приложений.
Чтобы убедиться, что все программные компоненты, из которых состоит конечный продукт, работают в унисон, их необходимо тестировать вместе. Вот тут-то и начинается интеграционное тестирование с Serenity BDD. Serenity BDD — это библиотека с открытым исходным кодом, которая помогает писать чище и удобнее в сопровождении. автоматические приемочные и регрессионные тесты.
:::Информация
BDD (Behaviour-Driven Development) — это метод тестирования, который включает в себя выражение того, как приложение должно вести себя, на простом бизнес-ориентированном языке.
Цель
Цель этой статьи — создать простое веб-приложение, которое пытается предсказать возраст человека по его имени. Затем, используя библиотеку Serenity BDD, напишите интеграционный тест, который гарантирует правильное поведение приложения.
Создание веб-приложения
Во-первых, основное внимание будет уделено серверной части Spring Boot. Конечная точка GET API будет отображаться с помощью Spring RestController. Когда конечная точка вызывается с именем человека, она возвращает предсказанный возраст для этого имени. Фактический прогноз будет обрабатываться agify.io.
Далее будет реализовано приложение Angular, предоставляющее пользователю ввод текста. Когда имя вводится во входные данные, на серверную часть будет отправлен HTTP-запрос GET для получения предсказания возраста. Затем приложение примет прогноз и отобразит его пользователю.
Полный код проекта для этой статьи доступен на [GitHub] (https://github.com/andreistefanwork/angular-spring-integration-test).
Создание бэкенда
Сначала будет определена модель прогнозирования возраста. Он будет иметь форму записи Java с именем и возрастом. Здесь также будет определен пустой прогноз возраста:
AgePrediction.java
```java
общедоступная запись AgePrediction (имя строки, целочисленный возраст) {
частный AgePrediction () {
это("", 0);
public static AgePrediction empty() {
вернуть новый AgePrediction();
RestController обрабатывает HTTP-вызовы к /age/prediction. Он определяет метод GET, который получает имя и обращается к api.agify.io для получения прогноза возраста. Метод помечен @CrossOrigin, чтобы разрешить запросы от Angular. Если параметр name не указан, метод просто возвращает пустой прогноз возраста.
Чтобы сделать фактический вызов прогноза, будет использоваться REST-клиент Spring — RestTemplate:
Модель прогнозирования возраста будет определена как интерфейс с «именем» и «возрастом»:
предсказание возраста.модель.тс
```машинопись
интерфейс экспорта AgePredictionModel {
имя: строка;
возраст: число;
Веб-страница будет состоять из текста <input>, где пользователи будут вводить имя, которое будет использоваться для предсказания возраста, и двух элементов <h3>, где будут отображаться имя и предсказанный возраст.
Когда пользователи вводят <input>, текст будет передан классу typescript через функцию onNameChanged($event).
Отображение name и предполагаемого возраста обрабатывается путем подписки на observable agePrediction$.
app.component.html
```разметка
<дел>
<input id="nameInput"
тип = "текст"
(вход)="onNameChanged($event)"/>
<дел>
Имя: {{(agePrediction$ | async).name}}
<дел>
Возраст: {{(agePrediction$ | async).age}}
Что касается компонента Angular, он будет вызываться, когда происходят изменения в <input> через функцию onNameChanged($event). Событие преобразуется в наблюдаемый объект с именем agePrediction$, который передается для запуска HTTP GET на серверную часть с самым последним именем. Это достигается за счет использования Subject nameSubject и операторов RxJs debounceTime, DifferentUntilChanged, switchMap, shareReplay.
:::Информация
debounceTime — выдает значение из источника Observable только после того, как прошел определенный промежуток времени без другого источника
distinctUntilChanged - испускает все значения, переданные исходным наблюдаемым объектом, если они различны по сравнению с последним значением, испускаемым наблюдаемым результатом
switchMap — проецирует каждое исходное значение в Observable, которое объединяется с выходным Observable, выдавая значения только из самого последнего спроецированного Observable.
shareReplay - поделиться источником и воспроизвести указанное количество выпусков по подписке
app.component.ts
```машинопись
@Составная часть({
селектор: 'приложение-корень',
Url-шаблона: './app.component.html',
styleUrls: ['./app.component.css']
класс экспорта AppComponent реализует OnInit {
статический только для чтения AGE_PREDICTION_URL = 'http://localhost:8080/age/prediction';
Получает модель прогнозирования возраста из нашего бэкенда Spring.
Имя @param, используемое для предсказания возраста
getAgePrediction = (имя: строка): Observable => {
const params = new HttpParams().set('имя', имя);
вернуть this.http.get(AppComponent.AGE_PREDICTION_URL,
{параметры});
onNameChanged($event) {
this.nameSubject.next($event.target.value);
Предварительный просмотр страницы предсказания возраста:
Написание интеграционного теста
В качестве первого шага тестирования веб-приложения создается абстрактный тестовый класс для инкапсуляции логики, необходимой для тестов Serenity:
Актер представляет человека или систему, использующую тестируемое приложение — здесь просто называется «тестер».
WebDriver — это интерфейс, используемый для управления веб-браузером. Указав аннотацию @Managed, Serenity внедрит экземпляр с конфигурацией по умолчанию в браузер
Внутри метода setBaseUrl() базовый URL-адрес, используемый для всех тестов, настраивается в EnvironmentVariables Serenity. Это сделано для того, чтобы избежать повторения протокола, хоста и порта для каждой тестовой страницы.
Абстрактинтегрионтест.java
```java
общедоступный абстрактный класс AbstractIntegrationTest {
Для тестирования страницы предсказания возраста создается новый класс IndexPage, наследуемый от PageObject (представление страницы в браузере). URL-адрес страницы относительно базового URL-адреса, указанного ранее, определяется с помощью аннотации @DefaultUrl.
HTML-элементы, присутствующие на странице, легко определяются с помощью Serenity Screenplay.
IndexPage.java
```java
@DefaultUrl("/")
открытый класс IndexPage расширяет PageObject {
общедоступная статическая финальная цель NAME_INPUT =
Наконец, написание интеграционного теста подразумевает класс, наследуемый от AbstractIntegrationTest, аннотированный с помощью @ExtendWith JUnit и расширения JUnit 5 от Serenity. indexPage будет введен Serenity во время выполнения теста. В стиле BDD тест структурирован в блоках «данное-когда-тогда».
Чтение того, что тест пытается достичь, почти так же просто, как чтение простого английского:
Оператор «данный» попытается открыть браузер на странице предсказания возраста.
Оператор ‘when’ получит дескриптор <input> и введет текст «Андрей».
Оператор then будет оценивать 4 оператора:
проверьте, видно ли имя человека <h3> на странице
проверьте, является ли имя человека, отображаемое на странице, ожидаемым
проверьте, отображается ли на странице возраст пользователя <h3>
проверьте, является ли возраст человека числом (не сверяясь с фиксированным возрастом, потому что прогноз возраста может измениться)
в конечном итоге обеспечивает более медленный ответ серверной части, ожидая 5 секунд перед прохождением/непрохождением условия теста.
IndexPageTest.java
```java
@ExtendWith(SerenityJUnit5Extension.класс)
открытый класс IndexPageTest расширяет AbstractIntegrationTest {
В статье кратко показано, как можно использовать Serenity BDD для реализации интеграционных тестов для современного веб-приложения. Количество настроек, необходимых для выполнения тестов, сведено к минимуму, а получившийся код для тестирования веб-страниц так приятно читать, что заставляет задуматься, как он вообще работает!
:::Информация
Я не спонсирован и не получил никакой компенсации от каких-либо продуктов/услуг/компаний, перечисленных выше. Эта статья предназначена исключительно для информационных целей.