Pull to refresh

Книга «Тестирование веб-API»

Reading time22 min
Views4.1K
imageПривет, Хаброжители!

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

«Тестирование веб-API» — это уникальное практическое руководство, включающее в себя описание всех этапов: от начального проектирования набора тестов до методов документирования, реализации и предоставления высококачественных API. Вы познакомитесь с обширным набором методов тестирования — от исследовательского до тестирования продакшен-кода, а также узнаете, как сэкономить время за счет автоматизации с использованием стандартных инструментов. Книга поможет избежать многих трудностей при тестировании API.
Для кого эта книга
При написании книги я попытался организовать материал так, чтобы помочь вам построить стратегию тестирования шаг за шагом. Однако в духе принципа применения разных стратегий для различных условий предлагается несколько способов, которыми вы можете воспользоваться, чтобы добиться успеха в тестировании.

Независимо от вашей мотивации, я настоятельно рекомендую прочитать часть 1 полностью. Глава 2 поможет освоиться в песочнице, которая используется в практических примерах. Она понадобится, если вы захотите опробовать различные методы, описанные в книге. Глава 3 обязательна к прочтению, потому что в ней подробно рассматриваются качество и риски, а также то, как они влияют на продукт. Я твердо убежден, что для успешного тестирования необходимо четко понимать, какую проблему вы пытаетесь решить. Если вы не знаете, в чем проблема, как вы можете быть уверены, что выбрали правильный подход, и как сможете оценить результат?

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

Новичкам в построении стратегии тестирования API


Книга построена таким образом, чтобы провести вас по всему пути с нуля до создания, реализации и выполнения успешной стратегии тестирования. Поэтому если вы новичок в тестировании API, последовательно изучайте главы, чтобы улучшать свои знания и навыки.

Улучшение существующей стратегии тестирования API


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

Реализация определенных методов тестирования


Некоторые читатели хотят узнать больше о конкретных техниках, и им не обязательно думать о более широкой картине (например, вам может быть поручено провести определенные тесты). Если ваша мотивация такова, я рекомендую сосредоточиться на интересующих вас темах, примерах и заданиях по тестированию. Другим проще понять, какое место занимает определенная техника в стратегии, попробовав ее, а затем уже расширять свое видение.

Разработка через приемочное тестирование


Как мы видели в предыдущих главах, различные виды тестирования помогают снизить риски, и автоматизация проверок API не является исключением. Созданные нами ранее автоматизированные проверки API были сосредоточены на рисках, которые мы определили на основе знаний о системе. Например, что, если мы не получим в ответе правильный код состояния либо изменим что-то в системе и это приведет к возвращению неправильного кода состояния? Эти риски относятся к категории «регрессионных», которые сосредоточены на том, что система может измениться нежелательным образом по мере добавления новых функций и модификаций ее кода.

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

В разработке через приемочное тестирование (ATDD, acceptance test-driven development) и разработке через тестирование (TDD, test-driven development) мы можем использовать одну и ту же модель:

  1. Создайте автоматическую проверку, которая заведомо будет провалена вследствие отсутствия соответствующей функциональности.
  2. Напишите продакшен-код.
  3. Проведите рефакторинг кода для удачного прохождения проверки.

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

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

  • Функция: отчеты о бронировании.
  • Сценарий: пользователь запрашивает общий доход по всем заказам:

    • Given: учесть возможность нескольких бронирований.
    • When: когда поступил запрос отчета об общих доходах.
    • Then: затем сообщается общая сумма, рассчитанная на основе всех заказов.

Этот сценарий, написанный с помощью языка Gherkin (Given-When-Then), может быть использован некоторыми средствами автоматизации для создания заведомо провальной автоматической проверки. Она не работает, потому что производственный код, который должна проверять, еще не существует. Но если мы напишем код, чтобы проверка прошла, то ее успех означает завершение работы — мы создали то, что нужно заказчику, и избежали риска расширения границ проекта.

Настройка автоматизированного фреймворка для приемочного тестирования


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

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.7.1</version>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>4.3.3</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.2</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.12.2</version>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>6.11.0</version>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-junit</artifactId>
        <version>6.11.0</version>
    </dependency>
</dependencies>

В дополнение к библиотекам, которые мы использовали в предыдущей главе, мы добавили две библиотеки из Cucumber. Это позволяет установить связь между нашими примерами сценариев и кодом автоматизации. Например, когда шаг «учесть возможность нескольких бронирований» будет выполнен Cucumber, он запустит автоматический код для создания нескольких бронирований. Для начала мы создадим фреймворк, аналогичный тому, который использовали ранее, со следующими пакетами в тестовой папке:
  • requests
  • payloads
  • stepdefinitions
В com.example мы создадим новый класс RunCukesTest, который содержит следующее:

@RunWith(Cucumber.class)
@CucumberOptions(
    features = {"src/test/resources"}
)
public class RunCukesTest {
}

Наконец, нужно добавить пример сценария в наш фреймворк, создав новый файл BokingReports.feature в папке resources, как показано ниже:

Feature: Booking reports

  Scenario: User requests total earnings of all bookings
    Given I have multiple bookings
    When I ask for a report on my total earnings
    Then I will receive a total amount based on all my bookings

Это означает, что когда мы запускаем mvn clean test, то получаем:

io.cucumber.junit.UndefinedStepException: The step "I have multiple bookings"
    is undefined. You can implement it using the snippet(s) below:

@Given("I have multiple bookings")
public void i_have_multiple_bookings() {
    // Напишите здесь код, который превращает приведенную выше фразу
    // в конкретные действия
    throw new io.cucumber.java.PendingException();
}

Класс RunCukesTest связывает JUnit и наши функциональные файлы вместе, позволяя Cucumber запускать файл BookingReports.feature в качестве теста. UndefinedStepException выводится, потому что когда Cucumber запускает файл функций, он ищет «определения шагов», которые соответствуют шагу в выполняемой проверке. Мы рассмотрим это подробнее ниже, а пока отметим, что это подтверждает готовность к созданию провальной автопроверки.

Создание заведомо провальной автоматической проверки


Теперь, когда у нас все готово, мы можем начать автоматизацию, создав новый класс в пакете сom.example.stepdefs под названием BookingReportsStepDefs со следующим кодом:

public class BookingReportsStepDefs {


    @Given("I have multiple bookings")
    public void i_have_multiple_bookings() {
        // Напишите здесь код, который превращает приведенную выше фразу
        // в конкретные действия
        throw new io.cucumber.java.PendingException();
    }

    @When("I ask for a report on my total earnings")
    public void i_ask_for_a_report_on_my_total_earnings() {
        // Напишите здесь код, который превращает приведенную выше фразу
        // в конкретные действия
        throw new io.cucumber.java.PendingException();
    }

    @Then("I will receive a total amount based on all my bookings")
    public void i_will_receive_a_total_amount_based_on_all_my_bookings() {
        // Напишите здесь код, который превращает приведенную выше фразу
        // в конкретные действия
        throw new io.cucumber.java.PendingException();
    }
}

Обратите внимание, что к каждому методу прикреплена аннотация, где в качестве параметра передается фраза. Первая аннотация

@Given("I have multiple bookings") "

(«у меня есть несколько бронирований») соответствует первой строке нашего сценария в файле BookingReports.feature:

Given I have multiple bookings

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

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

@Given("I have multiple bookings")
public void i_have_multiple_bookings() {
    BookingDates dates = new BookingDates(
        LocalDate.of(2021,01, 01),
        LocalDate.of(2021,03, 01)
    );

    Booking payloadOne = new Booking(
        "Mark",
        "Winteringham",
        200,
        true,
        dates,
        "Breakfast"
    );

    Booking payloadTwo = new Booking(
        "Mark",
        "Winteringham",
        200,
        true,
        dates,
        "Breakfast"
     );

     BookingApi.postBooking(payloadOne);
     BookingApi.postBooking(payloadTwo);
}

Далее необходимо создать POJO-объекты в com.example.payloads. Давайте создадим класс BookingDates и добавим в него следующее:

public class BookingDates {

    @JsonProperty
    private LocalDate checkin;
    @JsonProperty
    private LocalDate checkout;

    public BookingDates(LocalDate checkin, LocalDate checkout){
        this.checkin = checkin;
        this.checkout = checkout;
    }
}

Затем мы создадим класс Booking:

public class Booking {

    @JsonProperty
    private String firstname;
    @JsonProperty
    private String lastname;
    @JsonProperty
    private int totalprice;
    @JsonProperty
    private boolean depositpaid;
    @JsonProperty
    private BookingDates bookingdates;
    @JsonProperty
    private String additionalneeds;

    public Booking(String firstname, String lastname, int totalprice, boolean
     depositpaid, BookingDates bookingdates, String additionalneeds) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.totalprice = totalprice;
        this.depositpaid = depositpaid;
        this.bookingdates = bookingdates;
        this.additionalneeds = additionalneeds;
    }
}

И наконец, добавим код, необходимый для отправки нашего запроса в com.example.requests, в класс BookingApi:

public class BookingApi {

    private static final String apiUrl = "http://localhost:3000/booking/";

    public static Response postBooking(Booking payload) {
        return given()
                .contentType(ContentType.JSON)
                .body(payload)
                .when()
                .post(apiUrl);
    }
}

Если мы запустим автоматическую приемочную проверку с помощью команды mvn clean test, то увидим, что первый шаг сценария теперь выполняется, но второй шаг возвращает PendingException. Поэтому давайте заполним другие определения следующим образом:

private Response totalResponse;

@When("I ask for a report on my total earnings")
public void i_ask_for_a_report_on_my_total_earnings() {
    totalResponse = BookingApi.getTotal();
}

@Then("I will receive a total amount based on all my bookings")
public void i_will_receive_a_total_amount_based_on_all_my_bookings() {
    int total = totalResponse.as(Total.class).getTotal();

    assertEquals(total, 400);
}

Чтобы заставить этот код работать, нужно создать в com.example.payloads новый класс Total со следующим кодом:

public class Total {

    @JsonProperty
    private int total;

    public int getTotal() {
        return total;
    }
}

Также нужен новый метод в классе BookingApi для отправки запроса на конечную точку Total:

public static Response getTotal() {
    return given()
            .get(apiUrl + "report");
}

Это дает нам все необходимое для неудачной автоматической проверки. Когда мы запустим mvn clean test, то получим в ответ java.net.ConnectException: Operation timed out error.

Добиваемся прохождения автоматической проверки


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

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

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

Остерегайтесь ловушек


Здесь стоит повторить, что подход к проектированию на основе приемочных тестов отличается от других видов автоматизированного тестирования API, потому что основное внимание уделяется риску получить не то, что нужно. Цель ATDD — не исчерпывающая проверка всех рисков и всех комбинаций значений, которые может принимать API, и не замена TDD (они работают бок о бок). Она заключается в том, чтобы определить, чего хочет заказчик, и использовать это в качестве руководства к действию.

Чтобы такой подход приносил хорошие результаты, требуется время, зрелая команда и взвешенный подход. Легко попасть в ловушку, пытаясь использовать подход ATDD для создания и проверки всех функций. Прежде чем мы завершим этот раздел, я хочу поделиться несколькими примерами ловушек, в которые попадают команды при использовании ATDD. Надо иметь их в виду.

Пытаться все сделать в одиночку


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

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

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

Пытаться автоматизировать все


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

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

Моделирование (Mocking) веб-API


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

В качестве примера давайте рассмотрим автопроверку, написанную нами в главе 6, которая создаст бронирование, войдет в систему как администратор, а затем удалит бронирование:

@Test
public void deleteBookingReturns202(){

    BookingDates dates = new BookingDates(
        LocalDate.of( 2021 , 1 , 1 ),
        LocalDate.of( 2021 , 1 , 3 )
    );

    Booking payload = new Booking(
        1,
        "Mark",
        "Winteringham",
        true,
        dates,
        "Breakfast"
    );

    Response bookingResponse = BookingApi.postBooking(payload);
    BookingResponse createdBookingResponse =
     bookingResponse.as(BookingResponse.class);

    Auth auth = new Auth("admin", "password");

    AuthResponse authResponse = AuthApi.postAuth(auth).as(AuthResponse.class);

    Response deleteResponse = BookingApi.deleteBooking(
            createdBookingResponse.getBookingid(),
            authResponse.getToken());

assertEquals(202, deleteResponse.getStatusCode());
}

Проблема может возникнуть из-за того, что хотя проверка ориентирована на booking API, она использует auth API, чтобы убедиться, что мы можем авторизовать запрос на удаление. Это можно обобщить в модели зависимостей, показанной на рис. 8.1.

image

Успех процесса удаления бронирования зависит от auth API, поэтому проблемы с аутентификацией, будь то неправильно настроенные учетные записи пользователей, ошибки в auth API или проблемы со связью между API, приведут к сбою, который не обязательно связан с booking API. Это же может привести к постоянному сбою проверки, требующему обслуживания и отладки, что может снизить доверие к платформе.

К счастью, мы можем устранить влияние указанных проблем с помощью библиотек моделирования (мокинга) веб-API, которые помогают изолировать тестируемый веб-API, а также контролировать поток поступающей в него информации, как показано на рис. 8.2.

image

С помощью смоделированного веб-API мы можем определить, какие запросы и в каком формате он должен получать, а также какую информацию отправлять обратно. Чтобы лучше понять, как работает смоделированный веб-API, давайте рассмотрим, как мы можем обновить нашу проверку с помощью инструмента моделирования, или мокинга, — WireMock.

Подготовка к работе


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

Настройка booking api


Нам потребуется локально запущенная версия booking API, на которую мы будем отправлять запросы. Также нужно убедиться, что ничто больше не прослушивает порт 3004, потому что на его использование будет настроен поддельный auth API. Для этого загрузите в вашу IDE отдельную копию booking API из папки главы 8 в репозитории api-strategy-book-resources (http://mng.bz/827K). Теперь вы можете использовать IDE для создания и запуска booking API, выполнив метод main в классе BookingApplication.

Настройка нового кода автоматизации


Сначала мы создадим все необходимое для реализации имитации, начиная с нового примера проверки. Для этого добавьте новую проверку в класс BookingApiIT:

@Test
public void deleteBookingReturns202WithMocks(){
    BookingDates dates = new BookingDates(
        LocalDate.of( 2021 , 2 , 1 ),
        LocalDate.of( 2021 , 2 , 3 )
    );

    Booking payload = new Booking(
        1,
        "Mark",
        "Winteringham",
        true,
        dates,
        "Breakfast"
    );

    Response bookingResponse = BookingApi.postBooking(payload);
    BookingResponse createdBookingResponse =
     bookingResponse.as(BookingResponse.class);

    Response deleteResponse = BookingApi.deleteBooking(
            createdBookingResponse.getBookingid(),
            "abc123");

    assertEquals(202, deleteResponse.getStatusCode());
}

Обратите внимание, что мы убрали вызовы auth API и добавили жестко заданное значение для cookie, которое мы хотим отправить для метода deleteBooking. Если бы мы запустили эту проверку прямо сейчас, она, скорее всего, завершилась бы неудачей, поскольку abc123 не является действительным токеном. Однако мы исправим это, добавив в проверку макет API.
Библиотека мокинга WireMock позволяет программно создавать имитации в нашей кодовой базе. WireMock поставляется с целым рядом полезных функций. В нашем примере мы воспользуемся функциями заглушки, чтобы создать поддельный auth API с конечной точкой /validate, которая всегда будет отвечать OK, если отправлен правильный поддельный токен. Вы можете узнать больше о других возможностях WireMock на сайте wiremock.org.
Чтобы добавить WireMock, мы создадим зависимость в нашем файле POM.xml:

<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock-jre8</artifactId>
    <version>2.30.1</version>
    <scope>test</scope>
</dependency>

Построение смоделированной проверки


Теперь, когда API настроены и весь код на месте, давайте обновим нашу проверку, чтобы она использовала WireMock для auth API.

Обновление apiurl в booking api


Поскольку мы сейчас работаем с локально развернутой платформой restful-booker, первый шаг — обновить apiUrl в классе BookingAPI на следующий:

private static final String apiUrl = "http://localhost:3000/booking/";

Теперь мы будем отправлять запросы на локальный сервер localhost, а не на сервер automationintesting.online.

Установка wiremock


Теперь настроим WireMock так, чтобы он изображал auth API. Для этого требуется добавить следующий код:

image

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

Создание макета конечной точки


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

authMock.stubFor(post("/auth/validate")
                .withRequestBody(equalToJson("{ \"token\": \"abc123\" }"))
                .willReturn(aResponse().withStatus(200)));

Давайте рассмотрим каждый шаг:
  1. Мы вызываем authMock, который был создан в обработчике @BeforeAll до начала проверки.
  2. Чтобы создать имитацию конечной точки, вызываем команду stubFor и добавляем в нее в качестве параметра детали конфигурации конечной точки.
  3. Метод post() объявляет конечную точку POST, прослушивающую путь /auth/validate.
  4. withRequestBody() задает структуру и тип данных тела запроса. В данном примере мы говорим, что тело запроса должно соответствовать JSON-объекту {«token»: «abc123»}. Обратите внимание, что значение соответствует abc123, которое мы задали для deleteBooking().
  5. Метод willReturn позволяет указать, какой ответ будет получен, если запрос соответствует ожиданиям в нашей имитации конечной точки. В данном примере требуется только код состояния 200, который мы устанавливаем с помощью aResponse().withStatus(200).

Создание имитации конечной точки позволяет получить окончательный вариант проверки:

@Test
public void deleteBookingReturns202(){
    authMock.stubFor(post("/auth/validate")
            .withRequestBody(equalToJson("{ \"token\": \"abc123\" }"))
            .willReturn(aResponse().withStatus(200)));

    BookingDates dates = new BookingDates(
            LocalDate.of( 2021 , 2 , 1 ),
            LocalDate.of( 2021 , 2 , 3 )
    );

    Booking payload = new Booking(
            1,
            "Mark",
            "Winteringham",
            true,
            dates,
            "Breakfast"
    );


    Response bookingResponse = BookingApi.postBooking(payload);
    BookingResponse createdBookingResponse =
     bookingResponse.as(BookingResponse.class);

    Response deleteResponse = BookingApi.deleteBooking(
            createdBookingResponse.getBookingid(),
            "abc123");

    assertEquals(202, deleteResponse.getStatusCode());
}

Проверка отправляет запрос DELETE на /booking/{id}, который, в свою очередь, возьмет токен из cookie запроса и отправит его в смоделированный API. Если значение маркера совпадает с abc123, то имитация вернет правильное сообщение. В противном случае произойдет сбой, что приведет к ошибке проверки.

Теперь мы имеем больше контроля над поведением зависимостей booking API. Поэтому если мы столкнемся с какими-либо проблемами, то будем уверены, что они связаны именно с этим API.

Выполнение в составе пайплайна


На протяжении всей этой главы мы рассматривали нетрадиционные способы использования инструментов и библиотек для управления работой, улучшения ее стабильности и обратной связи. Но наступит момент, когда мы захотим запустить автоматизацию как часть пайплайна, который собирает, проверяет и развертывает систему. До сих пор мы выполняли большую часть работы в IDE либо используя развернутые экземпляры веб-API, либо запуская локальные версии веб-API перед выполнением проверок. Это вполне подходит для разработки автоматизации, но что нам делать, когда надо будет запускать проверки в пайплайне?

В отличие от проверки модулей, автоматизация веб-API требует, чтобы он был запущен и работал. Более того, если проверки выполняются как часть другого пайплайна, их нужно запустить в среде, например в блоке непрерывной интеграции (CI), которая отличается от нашей локальной среды. Для этого нам, скорее всего, потребуется внедрить какой-либо инструмент, библиотеку или скрипт. Подход здесь зависит от того, как организована наша кодовая база, какие инструменты мы используем для наших пайплайнов, и многого другого.

Давайте рассмотрим два разных сценария настройки запуска наших автоматических проверок. Один сценарий предполагает, что автоматизация интегрирована с нашей продуктовой кодовой базой, а другой — что автоматизация выделена в отдельный проект.

Автоматизация интегрирована с кодовой базой


Хотя не всегда можно сохранить код автоматизации в проект с рабочей кодовой базой, такое решение, безусловно, рекомендуется. Запуск автопроверок в рамках процесса сборки проекта (вспомните фазы тестирования и интегрированного тестирования в процессе сборки Maven) не только позволяет быстрее получить обратную связь, но и дает нам более широкий доступ и контроль над настройкой веб-API для запуска проверок.

Программный запуск


Воспользуемся наборами инструментов для тестирования, которые предлагаются в составе библиотек Spring Boot. Поскольку веб-API, с которыми мы работаем, являются частью развитого проекта, предлагающего необходимую основу и инструментарий для создания Java-веб-API, мы можем использовать инструмент spring-boot-starter-test, чтобы программно включить наш веб-API.

Давайте рассмотрим расширение примера с имитацией из предыдущего раздела, чтобы нам больше не нужно было самостоятельно «включать» booking API. Начнем с добавления необходимой зависимости в файл pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.5.4</version>
</dependency>

Со spring-boot-starter-test на месте мы можем добавить следующие аннотации в верхнюю часть нашего класса BookingApiIT:

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
    classes = BookingApplication.class)
@ActiveProfiles("dev")
public class BookingApiIT {

Давайте рассмотрим каждую аннотацию и посмотрим, как они помогают запустить веб-API:

  1. @ExtendWith(SpringExtension.class) используется для соединения Junit5 со Spring, чтобы мы могли передавать детали из класса Junit в веб-API, что мы и делаем в следующей строке.
  2. Обработчик @SpringBootTest служит для настройки запуска booking API. Мы используем два параметра: webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, который дает указание SpringBootTest загрузить определенный порт из нашего файла .properties, а также classes = BookingApplication.class, который сообщает SpringBootTest, какой класс содержит метод SpringApplication.run(), применяемый для запуска веб-API.
  3. Строка @ActiveProfiles(«dev») позволяет задать, с каким файлом .properties мы хотим загрузить веб-API. Это полезно, когда у нас есть различные файлы свойств, которые настраивают веб-API на «производственный» или «тестовый» режим (например, увеличение или уменьшение количества записей в журнал в зависимости от того, какое состояние требуется). Передавая параметр dev, мы загружаем в API файл application-dev.properties.

С этим кодом мы готовы запустить проверку deleteBookingReturns202() в нашем классе. Если booking API отключен, запустим проверку и увидим подробности запуска API в журнале.

Включение в пайплайн


Поскольку spring-boot-starter-test позаботился о настройке веб-API для автоматизации, процесс включения в пайплайн довольно прост. Предполагая, что в файле pom.xml установлен отказоустойчивый плагин (как показано далее в примере), мы запустим команду mvn clean install и увидим, как BookingApiIT запускается с booking веб-API:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.22.2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                    <configuration>
                        <includes>
                            <include>**/*IT</include>
                        </includes>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build

Запуск веб-API теперь интегрирован в процесс сборки Maven, и нам остается только настроить используемый инструментарий CI для запуска mvn clean install.

Автоматизация в отдельном проекте


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

Можно настроить веб-API программно, но это требует дополнительной работы. Давайте вернемся к примеру с booking API и представим, что на этот раз работаем с версией веб-API, скомпилированной в виде JAR-файла. Тогда нужно будет сделать две вещи:

  • включить booking веб-API;
  • выждать, пока booking веб-API будет готов к приему запросов.

С первым шагом можно справиться достаточно легко, воспользовавшись существующим кодом или скриптами, которые используются при развертывании системы. Это может быть что-то простое, например команда запуска JAR-файла в командной строке java-jar example.jar, или применение таких инструментов, как Docker, для запуска образа, содержащего наш веб-API (что выходит за рамки данной книги).

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

Есть много способов решения этой задачи. Например, можно создать утилиту, которая будет частью пайплайна (как приложение monitor, которое я создал на NodeJS для платформы restful-booker mng.bz/E0Rq). Или же можно расширить фреймворк автоматизации, добавив проверку того, что веб-API готовы к приему запросов, как показано в обработчике before, который можно добавить к BookingApiIT:

private static void waitForApi(String url, int timeoutLimit) throws
     InterruptedException {
    while(true){
        if(timeoutLimit == 0){
            fail("Unable to connect to Web API");
        }

        try{
            Response response = given().get(url);

            if(response.statusCode() != 200){
                timeoutLimit--;
                Thread.sleep(1000);
            } else {
                break;
            }
        } catch(Exception exception){
            timeoutLimit--;
            Thread.sleep(1000);
        }
     }
}

Метод waitForAPI принимает параметры url и timeoutLimit, которые используются для отправки запроса к выбранной конечной точке. Если запрос не проходит из-за ошибки соединения или ответ не приходит с кодом состояния 200, то метод отсчитывает время timeoutLimit и ждет секунду перед повторной отправкой запроса. Метод может завершиться одним из двух способов:

  1. TimeoutLimit достигает 0, и возникает сообщение fail, указывающее на проблему.
  2. Ответ 200 прерывает цикл while, а значит, можно начинать автоматическую проверку.

Мы можем вызвать метод в обработчике @BeforeAll:

waitForApi("http://localhost:3000/booking/actuator/health", 20);

Обработчик @BeforeAll теперь будет проверять, работает ли booking API, каждую секунду в течение 20 секунд. Если к этому времени веб-API не будет запущен, проверка завершится ошибкой с сообщением, что API недоступен.

Стоит повторить, что это всего лишь один из многих способов использования инструментов и/или библиотек, чтобы запустить наши веб-API. Основной вывод: нечто подобное надо предусмотреть в любой системе автоматизации проверок, которая не может сама активировать веб-API.

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

Итоги

  • Мы можем использовать автоматизированное приемочное тестирование для снижения рисков, предоставляя требуемые функции в нужное время.
  • Такие инструменты, как Cucumber, позволяют создавать сценарии Gherkin, которые привязываются к заведомо провальной автоматизации. Затем мы можем создать продакшен-код, чтобы сценарий был выполнен успешно.
  • В ситуациях, когда тестируемый веб-API зависит от других веб-API, возможны ошибки, не связанные с тестируемым веб-API.
  • Заменив зависимые веб-API такими инструментами, как WireMock, мы можем контролировать, какая информация отправляется в тестируемый веб-API, и уменьшить количество ошибок
  • Наши веб-API должны быть готовы к работе с автоматизацией проверок, что можно обеспечить программно.
  • Если автоматизированные проверки хранятся в том же проекте, что и производственная кодовая база, мы можем воспользоваться преимуществами инструментов и библиотек для запуска наших API, таких, например, как spring-boot-starter-test.
  • Если автоматизированные проверки работают отдельно от производственного кода, нам придется использовать инструменты или библиотеки для обеспечения работоспособности веб-API на момент запуска проверок.
Об авторе
Марк Винтерингем — тестировщик, системный программист и COO (главный операционный директор) Ministry of Testing с более чем десятилетним опытом экспертизы в области тестирования. Участвовал в технологических проектах в разных областях, которые были отмечены наградами BBC, Barclays, правительства Великобритании и Thomson Reuters. Сторонник современных методов тестирования, основанных на оценке рисков. Обучает команды методам автоматизации тестирования, разработки через тестирование и исследовательского тестирования. Соучредитель Ministry of Testing, сообщества, занимающегося вопросами образования в области тестирования.

Более подробно с книгой можно ознакомиться на сайте издательства:

» Оглавление
» Отрывок

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Для Хаброжителей скидка 25% по купону — API

Также обращаем ваше внимание на то, что у нас на сайте проходит распродажа.
Tags:
Hubs:
Total votes 13: ↑13 and ↓0+13
Comments0

Articles

Information

Website
piter.com
Registered
Founded
Employees
201–500 employees
Location
Россия