Фреймворк для тестування API сервісів на Java — приклад тестового завдання
Усі статті, обговорення, новини про тестування — в одному місці. Підписуйтеся на QA DOU!
Усім привіт! Нещодавно я отримав тестове завдання на позицію QA Automation Team Lead, в якому було необхідно написати тестовий фреймворк для тестування API сервісів на Java. Хоч це і не rocket science, проте мені здалося, що багатьом це може бути цікавим.
По технологіях фреймворк написаний на Java-стеці технологій. Використано maven, Lombok, testNG та Rest Assured.
По архітекторі фреймворк складається з двох шарів (layers), а саме: клієнтський (clients) та тестовий (test) шари. За потреби між ними можна додати бізнес шар (business), якщо API тести ближчі до end-to-end ніж до інтеграційних, або якщо є складна бізнес логіка.
Також за потреби можна додати cucumber шар.
Основна ідея — написати максимально узагальнений базовий клієнт (за допомогою Generics), який містить всі базові методи (GET, POST, PUT and DELETE) і може бути використаний для інтеракції з майже всіма ендпоінтами.
Такий підхід дозволяє зменшити кількість повторювання, а також додати централізоване управління логами Rest Assured (методи configureRequest та configureResponse) через базовий клієнт.
public class BaseAPIClient { //some code was cut public <T, K> ResponseWrapper<T> getEntity(Class<T> t, RequestWrapper<K> requestWrapper, String url) { ResponseWrapper<T> responseWrapper = new ResponseWrapper<>(); Response response = configureRequest(requestWrapper) .when() .get(url); responseWrapper.setBody(response.as(t)); responseWrapper.setResponseRaw(response); configureResponse(responseWrapper); return responseWrapper; } //some code was cut private <T> RequestSpecification configureRequest(RequestWrapper<T> requestWrapper) { RequestSpecification requestSpecification = RestAssured.given(); switch (logRequest()) { case "all": requestSpecification.log().all(); case "parameters": requestSpecification.log().parameters(); default: requestSpecification.log().method(); } if (requestWrapper.getHeaders() != null) { for (String key : requestWrapper.getHeaders().keySet()) { requestSpecification.header(key, requestWrapper.getHeaders().get(key)); } } if (requestWrapper.getQueryParameters() != null) { for (String key : requestWrapper.getQueryParameters().keySet()) { requestSpecification.queryParam(key, requestWrapper.getQueryParameters().get(key)); } } return requestSpecification; } //some code was cut }
Для того, щоб уніфікувати роботу з API клієнтом, створено дві обгортки RequestWrapper та ResponseWrapper.
Ці обгортки узагальненні (generalized) і їх тип визначається у тестах. ResponseWrapper містить і оригінальний респонс у вигляді окремого філду.
@Getter @Setter @Builder public class RequestWrapper<T> { private Map<String, String> headers; private Map<String, String> queryParameters; private T body; }
@Getter @Setter public class ResponseWrapper<T> { private T body; private Response responseRaw; public int getStatusCode() { return responseRaw.getStatusCode(); } }
BaseAPIClient можна використовувати для інтеракції з різними ендпоінтами, але я раджу створювати окремі клієнти на кожен ендпоінт, щоб логічно розділяти функціонал.
При потребі ці клієнти можна розширювати специфічними методами, такими як пошук та фільтрування. Також можна перезавантажувати базові методи.
public class PostsClient extends BaseAPIClient{ }
При написанні тестів я прихильник принципів ізоляції та керування станом аплікації, тому раджу створювати тестові сутності, які залучені в тесті, в before методі, та видаляти їх в after методі. Це збільшить надійність тестів в рази.
@BeforeMethod public void init() { postsClient = new PostsClient(); softAsserts = new SoftAssert(); Post post = Post.builder() .userId(1) .title("title") .body("body") .build(); RequestWrapper<Post> requestWrapper = RequestWrapper.<Post>builder() .headers(HEADERS) .body(post) .build(); responseWrapperPreconditions = postsClient.postEntity(Post.class, requestWrapper, URL); }
@AfterMethod public void cleanup() { RequestWrapper<Post> requestWrapper = RequestWrapper.<Post>builder() .headers(HEADERS) .body(responseWrapperPreconditions.getBody()) .build(); postsClient.deleteEntity(Post.class, requestWrapper , URL + responseWrapperPreconditions.getBody().getId()); }
Рекомендую розподіляти тести за принципом один REST метод один клас, оскільки прекондішни у такому підході будуть різними і досить великими. Також пропоную групувати перевірки по одному REST методу в одному тестовому методі для оптимізації, хоч це і дискусійне питання. Більш атомарний підхід також має право на життя.
@Test public void testGetPosts() { RequestWrapper<Post> requestWrapper = RequestWrapper.<Post>builder() .headers(HEADERS) .build(); ResponseWrapper<Post> responseWrapper = postsClient.getEntity(Post.class, requestWrapper , URL + responseWrapperPreconditions.getBody().getId()); softAsserts.assertEquals(responseWrapper.getStatusCode(), 200 , "Response status code should be 200"); softAsserts.assertTrue((isJsonSchemaValid("postsGetSchema.json", convertToStringJSON(responseWrapper.getBody()))) , "The response should have valid json schema"); softAsserts.assertEquals(responseWrapper.getBody().getId(), responseWrapperPreconditions.getBody().getId() , "Response id should be correct"); softAsserts.assertEquals(responseWrapper.getBody().getUserId(), responseWrapperPreconditions.getBody().getUserId() , "Response userId should be correct"); softAsserts.assertEquals(responseWrapper.getBody().getTitle(), responseWrapperPreconditions.getBody().getTitle() , "Response title should be correct"); softAsserts.assertEquals(responseWrapper.getBody().getBody(), responseWrapperPreconditions.getBody().getBody() , "Response body should be correct"); softAsserts.assertAll(); }
Оскільки тестовий API тільки симулює створення та видалення сутностей, то тест буде падати і має ілюстраційний характер.
Повний код фреймворку можна знайти за посиланням.
12 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів