Обзор best practices по использованию Rest Assured

Демонстрационный проект #

https://git.crtweb.ru/creative.qa/anarchy/mag/restassured-best-practices

Используйте спецификации #

Конструкция типа

given()
    .accept(ContentType.JSON)

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

public static RequestSpecification requestSpec() {
    return given()
        .accept(ContentType.JSON);
}

или билдер:

RequestSpecification requestSpec = new RequestSpecBuilder()
    .setAccept(ContentType.JSON)
    .build();

А в каждом запросе достаточно использовать только вызов этого метода или билдера.

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

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

public static ValidatableResponse responseSpecOk(Response response) {
    return response
        .then()
        .statusCode(200)
        .time(lessThanOrEqualTo(responseTime));
}

или билдер:

ResponseSpecification responseSpecOk = new ResponseSpecBuilder()
        .expectStatusCode(200)
        .expectResponseTime(lessThanOrEqualTo(responseTime))
        .build();

Храните эндпойнты в отдельном месте #

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

Тут есть несколько вариантов:

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

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

  • и, наконец, эндпойнты можно хранить в отдельном property-файле.

В случае, если эндпойнт включает в себя какое-то изменяемое значение (например, ID), можно пользоваться записью такого вида:

String endpoint = endpoint/{id}

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

.get(endpoint, id)

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

Выносите base URI в properties #

Так же, как и упомянутые выше заголовки, скорее всего часть URL повторяется в каждом запросе. Ее также можно вынести в отдельное место и объявить только один раз.

Для этого удобнее всего вынести baseURI в properties-файл и получать его значение через класс типа ManageProperties. Объявить baseURI можно двумя способами:

given()
    .baseUri(getProperty("baseUri"))

или

RestAssured.baseURI = getProperty("baseUri");

Что касается непосредственно самого вызова baseURI, тут тоже есть пара вариантов:

Первый – вызывать его в каждом запросе. Такой вариант выглядит более громоздко, но подойдет в том случае, если в определенных случаях нужно использовать другой base URI.

Второй – объявить его однократно, например, в спецификации запроса.

Используйте функционал Rest Assured для преобразования объектов #

Нет необходимости преобразовывать POJO в Json с помощью возможностей внешних библиотек, так как Rest Assured прекрасно справляется с этим самостоятельно. Достаточно добавить нужный POJO в тело запроса:

Pojo pojo = new Pojo();

given()
    .body(pojo)
    .post(URI);

Обратный процесс также работает:

Pojo pojo = given()
    .get(URI)
    .then
    .extract().body().as(Pojo.class)

Кроме того, Rest Assured умеет преобразовывать в Json и HashMap, в том числе и вложенные:

Map <Object, Object> outerMap = new HashMap<Object, Object>();
Map <Object, Object> innerMap = new HashMap<Object, Object>();

innerMap.put("key", "value");

outerMap.put("key", "value");
outerMap.put("innerMap", innerMap);

given()
    .body(outerMap)
    .post(URI);

Используйте валидацию Json-схемы #

Валидация Json-схемы - это простой и надежный способ убедиться, что полученные данные соответствуют ожидаемому формату. Json-схема имеет следующий вид:

{
    "type": "object",
    "properties": {
        "id": {
            "type": "integer"
        },
        "item": {
            "type": "object",
            "properties": {
                "key1": {
                    "type": ["string", "integer"]
                },
                "key2": {
                    "type": "string"
                }

            },
            "required": ["key1", "key2"]
        }
    },
    "required": ["id", "item"]
}

Создавать Json-схемы можно и вручную, но из-за не слишком человекоориентированного формата это может быть довольно утомительно, так что куда проще использовать для этого любой из многочисленных автоматических инструментов. Например: https://www.liquid-technologies.com/online-json-to-schema-converter

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

Для использования валидации Json-схем понадобится установить библиотеку json-schema-validator (в данном случае используется библиотека от io.rest-assured) и использовать следующий метод:

.body(matchesJsonSchemaInClasspath("schema.json"));

Выносите повторяющиеся действия в отдельные методы #

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

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

public static RequestSpecification requestSpec = new RequestSpecBuilder()
    .setBaseUri(BASE_URI)
    .setContentType(ContentType.JSON)
    .setAccept(ContentType.JSON)
    .build();

public static ResponseSpecification responseSpec(int statusCode) {
    return new ResponseSpecBuilder()
            .expectStatusCode(statusCode)
            .expectResponseTime(lessThanOrEqualTo(responseTime))
            .build();
}

public static Response createItem(Pojo body) {
    return given()
        .spec(requestSpec)
        .body(body)
        .post(Uri);
    }

public static ValidatableResponse createItem_valid(Response response) {
    return response
            .then()
            .spec(responseSpec(200))
            .body(matchesJsonSchemaInClasspath("schemas/CreateItem.json"));
}

@Test
public void createItemTest() {
    createItem_valid(
        createItem(body))
}

Используйте Hamcrest Matchers #

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

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

.body("id", equals(getId()))
.body("id", greaterThanOrEqualTo(1))
.body("items", either(hasItem("a")).and(hasItem("b"))

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

С полной документацией к мэтчерам можно ознакомиться здесь: http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html

Используйте методы для добавления заголовков #

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

given()
	.contentType(ContentType.JSON)
	.accept(ContentType.JSON)

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

Однако стоит обратить внимание на то, что при таком оформлении заголовков в Accept помимо application/json также передаются application/javascript, text/javascript и text/json. В некоторых API это может привести к ошибкам, и в таком случае enum ContentType стоит заменить на строку с указанием конкретного типа данных.

Проверяйте статус-код без assertThat #

Запись типа

.assertThat(statusCode(200))

просто излишня. Эта строка выполняет ту же самую работу:

.statusCode(200)

Передавайте статус-коды в качестве аргумента #

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

public static ResponseSpecification responseSpecError(int statusCode) {
    return new ResponseSpecBuilder()
        .expectStatusCode(statusCode)
        .expectResponseTime(lessThanOrEqualTo(responseTime))
        .build();
}

Используйте логирование для отладки тестов #

Метод .log() позволяет получить информацию как об отправленном запросе, так и о полученном ответе. В первом случае необходимо его применить до отправки запроса (get, post и т.д.), во втором случае – после.

Вам доступны следующие методы:

.log().all()
.log().params()
.log().body()
.log().headers()
.log().cookies()

Однако оставлять логирование в тестах, которые уже дописаны и корректно работают, не стоит – объем в информации в логах может быть настолько большим, что результаты всех тестов перестанут умещаться в консоли IDE, не говоря уже о том, что просматривать результаты тестов в таком случае будет очень неудобно.

Увидел(а) ошибку в тексте? Нет нужной информации или она не полная?
Скорей же исправь данный недочет и облегчи жизнь себе и своей команде!
Обязательно ознакомься с тем как заполнить bugаж знаний и после создавай МР в проекте bugаж знаний на своего QA Team Lead.