HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-03-10
1 <p>Мы уже<a>рассказывали</a>о преимуществах REST-assured - одной из самых популярных Java-библиотек, которая предназначена для автоматизации тестирования REST-API. Следующие советы позволят вам использовать этот инструмент более эффективно.</p>
1 <p>Мы уже<a>рассказывали</a>о преимуществах REST-assured - одной из самых популярных Java-библиотек, которая предназначена для автоматизации тестирования REST-API. Следующие советы позволят вам использовать этот инструмент более эффективно.</p>
2 <h2>1. Выносите end-point'ы в отдельное место</h2>
2 <h2>1. Выносите end-point'ы в отдельное место</h2>
3 <p>С одной стороны, это очевидно. Но увы, до сих пор нередко встречается код, имеющий захардкоженные end-point'ы в запросе. В целом же, лучше выносить end-point'ы в статические константы финального класса. Но тут есть нюанс - избегайте антипаттерна "константный интерфейс", т. к. это является плохой практикой. И помните, что REST-assured даёт возможность выносить параметры пути, к примеру:</p>
3 <p>С одной стороны, это очевидно. Но увы, до сих пор нередко встречается код, имеющий захардкоженные end-point'ы в запросе. В целом же, лучше выносить end-point'ы в статические константы финального класса. Но тут есть нюанс - избегайте антипаттерна "константный интерфейс", т. к. это является плохой практикой. И помните, что REST-assured даёт возможность выносить параметры пути, к примеру:</p>
4 public final class EndPoints { public static final String users = "/users/{id}"; ... } given().pathParams("id", someId).get(EndPoints.users)...; // или так given().get(EndPoints.users, someId)....<p>Кроме того, если вы во многих запросах применяете одинаковый базовый путь, считается хорошей практикой выносить его в отдельную константу, передавая в<strong>basePath</strong>, как в примере ниже:</p>
4 public final class EndPoints { public static final String users = "/users/{id}"; ... } given().pathParams("id", someId).get(EndPoints.users)...; // или так given().get(EndPoints.users, someId)....<p>Кроме того, если вы во многих запросах применяете одинаковый базовый путь, считается хорошей практикой выносить его в отдельную константу, передавая в<strong>basePath</strong>, как в примере ниже:</p>
5 // имеем url приложения http://host:port/appname/rest/someEndpoints private static final basePath = "/appname/rest/"; .. // можно задать базовый путь на глобальном уровне, // и он будет использоваться ко всем запросам: RestAssured.basePath = basePath; // либо на уровне одного запроса: given().basePath(basePath)... // либо на уровне спецификации, но об этом поговорим позже<p>То же применимо как к хосту, так и к порту тестируемого программного приложения.</p>
5 // имеем url приложения http://host:port/appname/rest/someEndpoints private static final basePath = "/appname/rest/"; .. // можно задать базовый путь на глобальном уровне, // и он будет использоваться ко всем запросам: RestAssured.basePath = basePath; // либо на уровне одного запроса: given().basePath(basePath)... // либо на уровне спецификации, но об этом поговорим позже<p>То же применимо как к хосту, так и к порту тестируемого программного приложения.</p>
6 <h2>2. ContentType/Accept</h2>
6 <h2>2. ContentType/Accept</h2>
7 <p>Эти заголовки применяются почти во всех HTTP-запросах. Разработчики REST-assured прекрасно это понимают, поэтому они обеспечили возможность их установки посредством вызова специальных методов:</p>
7 <p>Эти заголовки применяются почти во всех HTTP-запросах. Разработчики REST-assured прекрасно это понимают, поэтому они обеспечили возможность их установки посредством вызова специальных методов:</p>
8 // вот то плохая практика написания: given().header("content-type", "application/json").header("accept", "application/json")...; // а вот эта хорошая: given().contentType(ContentType.JSON).accept(ContentType.JSON)...;<p>Хорошей практикой станет установка данных заголовков в спецификации либо на глобальном уровне. Результат - повышение читабельности кода.</p>
8 // вот то плохая практика написания: given().header("content-type", "application/json").header("accept", "application/json")...; // а вот эта хорошая: given().contentType(ContentType.JSON).accept(ContentType.JSON)...;<p>Хорошей практикой станет установка данных заголовков в спецификации либо на глобальном уровне. Результат - повышение читабельности кода.</p>
9 <h2>3. StatusCode и т. п.</h2>
9 <h2>3. StatusCode и т. п.</h2>
10 <p>Для выполнения проверки каждой составляющей HTTP-ответа, библиотека REST-assured предоставляет удобный синтаксис. Но на практике часто можно увидеть следующий код:</p>
10 <p>Для выполнения проверки каждой составляющей HTTP-ответа, библиотека REST-assured предоставляет удобный синтаксис. Но на практике часто можно увидеть следующий код:</p>
11 // это плохая практика написания: Response response = given()...when().get(someEndpoint); Assert.assertEquals(200, response.then().extract().statusCode()); //а это хорошая: given()...when().get(someEndpoint).then().statusCode(200);<h2>4. Используйте спецификации</h2>
11 // это плохая практика написания: Response response = given()...when().get(someEndpoint); Assert.assertEquals(200, response.then().extract().statusCode()); //а это хорошая: given()...when().get(someEndpoint).then().statusCode(200);<h2>4. Используйте спецификации</h2>
12 <p>Мы знаем, что дублировать код не есть хорошо. Для уменьшения дублирования нужно использовать спецификации. В REST-assured есть возможность создания спецификации и для запроса, и для ответа. В спецификацию запроса можно вынести всё, что можно продублировать в запросах.</p>
12 <p>Мы знаем, что дублировать код не есть хорошо. Для уменьшения дублирования нужно использовать спецификации. В REST-assured есть возможность создания спецификации и для запроса, и для ответа. В спецификацию запроса можно вынести всё, что можно продублировать в запросах.</p>
13 RequestSpecification requestSpec = new RequestSpecBuilder() .setBaseUri("http://localhost") .setPort(8080) .setAccept(ContentType.JSON) .setContentType(ContentType.ANY) ... .log(LogDetail.ALL) .build(); // мы можем задать одну спецификацию для всех запросов: RestAssured.requestSpecification = requestSpec; // либо для отдельного: given().spec(requestSpec)...when().get(someEndpoint);<p>Кроме того, в спецификацию ответа можно выносить все проверки, дублируемые от запроса к запросу.</p>
13 RequestSpecification requestSpec = new RequestSpecBuilder() .setBaseUri("http://localhost") .setPort(8080) .setAccept(ContentType.JSON) .setContentType(ContentType.ANY) ... .log(LogDetail.ALL) .build(); // мы можем задать одну спецификацию для всех запросов: RestAssured.requestSpecification = requestSpec; // либо для отдельного: given().spec(requestSpec)...when().get(someEndpoint);<p>Кроме того, в спецификацию ответа можно выносить все проверки, дублируемые от запроса к запросу.</p>
14 ResponseSpecification responseSpec = new ResponseSpecBuilder() .expectStatusCode(200) .expectBody(containsString("success")) .build(); // мы можем задать одну спецификацию для всех ответов: RestAssured.responseSpecification = responseSpec; // либо для отдельного: given()...when().get(someEndpoint).then().spec(responseSpec)...;<p>Также мы можем создавать несколько спецификаций для различных типов запросов/ответов и применять их в нужном случае.</p>
14 ResponseSpecification responseSpec = new ResponseSpecBuilder() .expectStatusCode(200) .expectBody(containsString("success")) .build(); // мы можем задать одну спецификацию для всех ответов: RestAssured.responseSpecification = responseSpec; // либо для отдельного: given()...when().get(someEndpoint).then().spec(responseSpec)...;<p>Также мы можем создавать несколько спецификаций для различных типов запросов/ответов и применять их в нужном случае.</p>
15 <h2>5. Не надо писать собственные костыли для преобразования объектов</h2>
15 <h2>5. Не надо писать собственные костыли для преобразования объектов</h2>
16 <p>Не нужно преобразовывать свои POJO в JSON, используя Jackson ObjectMapper'а, а потом передавать полученную строку в тело запроса. Почему? Потому что с этой задачей отлично справляется библиотека REST-assured. Причём применяется всё тот же Jackson либо Gson с учётом того, что находится в classpath. А чтобы выполнить преобразование в XML, применяется JAXB. Что касается исходного формата, то он определяется автоматически по значению Content-Type.</p>
16 <p>Не нужно преобразовывать свои POJO в JSON, используя Jackson ObjectMapper'а, а потом передавать полученную строку в тело запроса. Почему? Потому что с этой задачей отлично справляется библиотека REST-assured. Причём применяется всё тот же Jackson либо Gson с учётом того, что находится в classpath. А чтобы выполнить преобразование в XML, применяется JAXB. Что касается исходного формата, то он определяется автоматически по значению Content-Type.</p>
17 given().contentType(ContentType.JSON).body(somePojo) .when().post(EndPoints.add) .then() .statusCode(201); // то же самое действует и в обратную сторону: SomePojo pojo = given(). .when().get(EndPoints.get) .then().extract().body().as(SomePojo.class);<p>Вдобавок к вышесказанному, REST-assured хорошо справляется и с преобразованием HashMap в JSON и в обратном порядке.</p>
17 given().contentType(ContentType.JSON).body(somePojo) .when().post(EndPoints.add) .then() .statusCode(201); // то же самое действует и в обратную сторону: SomePojo pojo = given(). .when().get(EndPoints.get) .then().extract().body().as(SomePojo.class);<p>Вдобавок к вышесказанному, REST-assured хорошо справляется и с преобразованием HashMap в JSON и в обратном порядке.</p>
18 <h2>6. Используйте всю силу Groovy</h2>
18 <h2>6. Используйте всю силу Groovy</h2>
19 <p>Так как сама библиотека написана на Groovy, она даёт возможность использовать к полученному JSON/XML-ответу разные методы из Groovy. Допустим:</p>
19 <p>Так как сама библиотека написана на Groovy, она даёт возможность использовать к полученному JSON/XML-ответу разные методы из Groovy. Допустим:</p>
20 // методы find, findAll используются к коллекции для поиска первого и всех вхождений, а метод collect - для создания новой коллекции из найденных результатов. // переменная it создается неявно, указывая на текущий элемент коллекции Map&lt;String, ?&gt; map = get(EndPoints.anyendpoint).path("rootelement.find { it.title =~ 'anythingRegExp'}"); // можно явно задать название переменной, указывающей на текущий элемент Map&lt;String, ?&gt; map = get(EndPoints.anyendpoint).path("rootelement.findAll { element -&gt; element.title.length() &gt; 4 }"); // можно применять методы max, min, sum в целях суммирования всех значений коллекции и поиска максимального и минимально значений String expensiveCar = get(EndPoints.cars).path("cars.find { it.title == 'Toyota Motor Corporation'}.models.max { it.averagePrice }.title");<p>Применение методов из Groovy сократит количество кода, написанного вами в целях поиска необходимого значения из ответа.</p>
20 // методы find, findAll используются к коллекции для поиска первого и всех вхождений, а метод collect - для создания новой коллекции из найденных результатов. // переменная it создается неявно, указывая на текущий элемент коллекции Map&lt;String, ?&gt; map = get(EndPoints.anyendpoint).path("rootelement.find { it.title =~ 'anythingRegExp'}"); // можно явно задать название переменной, указывающей на текущий элемент Map&lt;String, ?&gt; map = get(EndPoints.anyendpoint).path("rootelement.findAll { element -&gt; element.title.length() &gt; 4 }"); // можно применять методы max, min, sum в целях суммирования всех значений коллекции и поиска максимального и минимально значений String expensiveCar = get(EndPoints.cars).path("cars.find { it.title == 'Toyota Motor Corporation'}.models.max { it.averagePrice }.title");<p>Применение методов из Groovy сократит количество кода, написанного вами в целях поиска необходимого значения из ответа.</p>
21 <p><a>Источник</a></p>
21 <p><a>Источник</a></p>
22  
22