Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

«Автоматизация функционального тестирования REST API: секреты, тонкости и подводные камни» – Павел Асанов, 2ГИС

1,388 views

Published on

Павел Асанов, 2ГИС (SQA Days-17, 29-30 мая 2015 г., Минск).

Published in: Technology
  • Login to see the comments

«Автоматизация функционального тестирования REST API: секреты, тонкости и подводные камни» – Павел Асанов, 2ГИС

  1. 1. Автоматизация функционального тестирования REST API секреты, тонкости и подводные камни
  2. 2. Павел Асанов QA Lead
  3. 3. 90 городов 700 тыс. пользователей 12 млн просмотров 1 млн отзывов 3 млн посетителей
  4. 4. API как продукт
  5. 5. API как продукт 80% 20%
  6. 6. Регрессия 3 месяца 5 минут
  7. 7. ● Ресурсы, однозначно определяемые по URL ● Представления ресурсов (JSON) ● Методы работы с ресурсами REST - это... POST /reviews GET /reviews/666 PUT /reviews/666 PATCH /reviews/666 DELETE /reviews/666
  8. 8. Что тестируем?
  9. 9. 1. Логика
  10. 10. отсутствие обязательных параметров невалидный протокол параметры запроса скрытыйобычный скрытый с особым типом авторизованный гость БАК удалённыйзаблокированный забаненный филиал БАК автор неактивированный удалённый обычный корректные с фото невалидные значения параметров невалидные типы параметров БАК expired другой юзер приватныйконтент пользователь аксесс токен
  11. 11. отсутствие обязательных параметров невалидный протокол параметры запроса скрытыйобычный скрытый с особым типом авторизованный гость БАК удалённыйзаблокированный забаненный филиал БАК автор неактивированный удалённый обычный корректные с фото невалидные значения параметров невалидные типы параметров БАК expired другой юзер 201 Авторизованный пользователь добавляет отзыв с фото приватныйконтент пользователь аксесс токен POST
  12. 12. отсутствие обязательных параметров невалидный протокол параметры запроса скрытыйобычный скрытый с особым типом авторизованный гость БАК удалённыйзаблокированный забаненный филиал БАК автор неактивированный удалённый обычный корректные с фото невалидные значения параметров невалидные типы параметров БАК expired другой юзер 200 Авторизованный владелец Бизнес-аккаунта запрашивает приватное обращение приватныйконтент пользователь аксесс токен GET
  13. 13. отсутствие обязательных параметров невалидный протокол параметры запроса скрытыйобычный скрытый с особым типом авторизованный гость БАК удалённыйзаблокированный забаненный филиал БАК автор неактивированный удалённый обычный корректные с фото невалидные значения параметров невалидные типы параметров БАК expired другой юзер 401 Гость добавляет отзыв приватныйконтент пользователь аксесс токен POST
  14. 14. отсутствие обязательных параметров невалидный протокол параметры запроса скрытыйобычный скрытый с особым типом авторизованный гость БАК удалённыйзаблокированный забаненный филиал БАК автор неактивированный удалённый обычный корректные с фото невалидные значения параметров невалидные типы параметров БАК expired другой юзер 403 Авторизованный пользователь запрашивает чужое приватное обращение приватныйконтент пользователь аксесс токен GET
  15. 15. отсутствие обязательных параметров невалидный протокол параметры запроса скрытыйобычный скрытый с особым типом авторизованный гостьаксесс токен БАК удалённыйзаблокированный забаненный филиал БАК автор неактивированный удалённый обычный корректные с фото невалидные значения параметров невалидные типы параметров БАК expired другой юзер 500 400 400 404 Невалидный запрос приватныйконтент пользователь
  16. 16. 2. Данные и их формат
  17. 17. { code: 200 status: "success" review: { filial_id: "985690699651034" text: "Для меня Code Fest - это глоток свежего воздуха. Только надо баги пофиксить." is_recommended: false project_id: 1 source: "flamp" rating: 4 date_created: "2011-03-28T05:30:50+04:00" user_id: 171 photo: null } }
  18. 18. { code: 200 status: "success" review: { filial_id: "985690699651034" text: "Для меня Code Fest - это глоток свежего воздуха. Только надо баги пофиксить." is_recommended: false project_id: 1 source: "flamp" rating: 4 date_created: "2011-03-28T05:30:50+04:00" user_id: 171 photo: null } } точное значение
  19. 19. { code: 200 status: "success" review: { filial_id: "985690699651034" text: "Для меня Code Fest - это глоток свежего воздуха. Только надо баги пофиксить." is_recommended: false project_id: 1 source: "flamp" rating: 4 date_created: "2011-03-28T05:30:50+04:00" user_id: 171 photo: null } } точное значение тип значения и его диапазон
  20. 20. { code: 200 status: "success" review: { filial_id: "985690699651034" text: "Для меня Code Fest - это глоток свежего воздуха. Только надо баги пофиксить." is_recommended: false project_id: 1 source: "flamp" rating: 4 date_created: "2011-03-28T05:30:50+04:00" user_id: 171 photo: null } } точное значение enum тип значения и его диапазон
  21. 21. { code: 200 status: "success" review: { filial_id: "985690699651034" text: "Для меня Code Fest - это глоток свежего воздуха. Только надо баги пофиксить." is_recommended: false project_id: 1 source: "flamp" rating: 4 date_created: "2011-03-28T05:30:50+04:00" user_id: 171 photo: null } } точное значение enum тип значения и его диапазон формат значения
  22. 22. { code: 200 status: "success" review: { filial_id: "985690699651034" text: "Для меня Code Fest - это глоток свежего воздуха. Только надо баги пофиксить." is_recommended: false project_id: 1 source: "flamp" rating: 4 date_created: "2011-03-28T05:30:50+04:00" user_id: 171 photo: null } } точное значение наличие атрибута enum тип значения и его диапазон формат значения
  23. 23. 3. Изменение состояния системы
  24. 24. ● После DELETE сущность стала недоступна ● После логаута access token удаляется
  25. 25. ● После DELETE сущность стала недоступна ● После логаута access token удаляется запрос сущности возвращает 404
  26. 26. ● После DELETE сущность стала недоступна ● После логаута access token удаляется запрос с данным токеном возвращает 401 запрос сущности возвращает 404
  27. 27. ● После DELETE сущность стала недоступна ● После логаута access token удаляется запрос с данным токеном возвращает 401 или запрос сущности возвращает 404 access token отсутствует в БД
  28. 28. Глава первая, в которой мы знакомимся с JSON-schema
  29. 29. JSON-схема — это… 1) готовая документация + примеры
  30. 30. header: "Информация об отзыве", description: "Информация об отзыве", request: { type: "GET", url: "https://flamp.ru/api/2.0/reviews/{id}", properties: { id: { type: "integer", required: true, examples: ["1"], description: "Идентификатор отзыва" } } },
  31. 31. response: { type: "object", required: true, properties: { code: "{{ common/code }}", status: "{{ common/status }}", review: "{{ objects/review }}" } }, ...
  32. 32. 2) валидация запроса на сервере
  33. 33. 3) валидация ответа на сервере в dev-режиме
  34. 34. + простейший smoke-тест + полная проверка формата
  35. 35. + простейший smoke-тест + полная проверка формата - white-box - схема может содержать ошибки - нельзя использовать на продакшне
  36. 36. Глава вторая, в которой мы добавляем assertions в JSON- schema
  37. 37. assertions: [ { name: "View Review #1", request: { id: 1 }, response: { status: "success", review: { id: 1, date_created: "_is_datetime", date_edited: "_is_datetime|null" } } }, ]
  38. 38. assertions: [ { name: "View Review #99999999", request: { id: 99999999 }, response: { status: "error" } } ]
  39. 39. Flamposcope
  40. 40. + smoke testing + выполняется ~20 сек + тесты пишет разработчик
  41. 41. + smoke testing + выполняется ~20 сек + тесты пишет разработчик - только GET - захардкоженные данные - тесты пишет разработчик
  42. 42. Глава третья, в которой мы хотели прикрутить к JSON-схеме UI
  43. 43. + удобный UI + нет кода + автоподстановка из схемы
  44. 44. + удобный UI + нет кода + автоподстановка из схемы - серый ящик - фикстуры - поддержка UI
  45. 45. Глава четвёртая, в которой мы начали писать на PHP
  46. 46. Параметризованные тесты https://flamp.ru/api/2.0/filials/?what=кафе&project=1&lon=71&lat=65&radius=1000& sort=relevance&limit=12&page=5&with_markers=false&building_id=1&metarubric=323& depth=1&scopes=reviews&access_token=fa07c31442508e2248fcd634a786d409428ca50d
  47. 47. Параметризованные тесты https://flamp.ru/api/2.0/filials/?what=кафе&project=1&lon=71&lat=65&radius=1000& sort=relevance&limit=12&page=5&with_markers=false&building_id=1&metarubric=323& depth=1&scopes=reviews&access_token=fa07c31442508e2248fcd634a786d409428ca50d [params_set_1] => 200, [params_set_2] => 404, [params_set_3] => 401, [params_set_4] => 403, [params_set_5] => 400, [params_set_6] => 500, ...
  48. 48. Параметризованные тесты https://flamp.ru/api/2.0/filials/?what=кафе&project=1&lon=71&lat=65&radius=1000& sort=relevance&limit=12&page=5&with_markers=false&building_id=1&metarubric=323& depth=1&scopes=reviews&access_token=fa07c31442508e2248fcd634a786d409428ca50d [params_set_1] => 200, [params_set_2] => 404, [params_set_3] => 401, [params_set_4] => 403, [params_set_5] => 400, [params_set_6] => 500, ... набор N ... набор 1 тест
  49. 49. 2fingers = PHPUnit + сomposer + http-клиент (Guzzle поверх cURL) + данные
  50. 50. данные dataProvider параметры проверки Структура теста test
  51. 51. Тестовые наборы Тестовые наборы Тестовые наборы Данные Установка параметров запроса источник данных Проверки если success ожидаемый код ожидаемый результат фактический результат ожидаемый код роль параметры запроса объект запрос код ответа ожидаемый ответ тело ответа
  52. 52. Данные фикстуры дамп
  53. 53. ● реальные ● случайные, но однородные ● уникальные Требования к данным
  54. 54. Работа с БД $entity = Db()->entity('review')->forFilial('141265770608749') ->isHidden(false)->withPhoto(true)->getRandomEntity(); $user_id = Db()->user()->withStatus(1)->getRandomUser()->id; $article = Db()->table('articles')->isPublished(true)->getRow();
  55. 55. Всегда ли? 'bacs' => [ 'flamp_nsk' => 1, 'flamp_krsk' => 4, 'flamp_msk' => 13, ], 'filials' => [ 'pac' => '141265770608749', 'pac2' => '141265771836316', 'bac' => '141265771910841', 'simple' => '141265771459351', ], config.php
  56. 56. assertions $this->assertEquals($user_id, $actual->user_id, “incorrect user_id”); $this->assertEquals($project_id, $actual->project_id, “incorrect project_id”); $this->assertEquals($id, $actual->id, “incorrect id”); ... $this->assert($expected, $actual);
  57. 57. // Фактический результат $actual = $this->getResponseBody(); // Ожидаемый результат $expected = [ 'user' => CHECK_STRING_NOT_EMPTY, 'access_token' => CHECK_STRING_NOT_EMPTY ]; // Сравниваем ФР и ОР $this->assert($expected, $actual); assertions
  58. 58. // Ожидаемый результат $expected = [ 'filial_id' => $filial_id, 'user_id' => $user_id, 'text' => $text, 'date_created' => CHECK_DATETIME_FORMAT, 'date_edited' => null, 'comments_count' => 0, 'likes_score' => 0, 'source' => CHECK_SOURCE, 'filial' => CHECK_NOT_NULL, 'user' => CHECK_EXIST, 'comments' => CHECK_NOT_NULL, 'official_answer' => null, 'additional_data' => [ 'is_liked' => false, 'is_subscribed_to_comments' => false ], 'url' => CHECK_STRING_NOT_EMPTY, 'id' => CHECK_POSITIVE ];
  59. 59. // Ожидаемый результат $expected = [ 'filial_id' => $filial_id, 'user_id' => $user_id, 'text' => $text, 'date_created' => CHECK_DATETIME_FORMAT, 'date_edited' => null, 'comments_count' => 0, 'likes_score' => 0, 'source' => CHECK_SOURCE, 'filial' => CHECK_NOT_NULL, 'user' => CHECK_EXIST, 'comments' => CHECK_NOT_NULL, 'official_answer' => null, 'additional_data' => [ 'is_liked' => false, 'is_subscribed_to_comments' => false ], 'url' => CHECK_STRING_NOT_EMPTY, 'id' => CHECK_POSITIVE ]; точное значение точное значение
  60. 60. // Ожидаемый результат $expected = [ 'filial_id' => $filial_id, 'user_id' => $user_id, 'text' => $text, 'date_created' => CHECK_DATETIME_FORMAT, 'date_edited' => null, 'comments_count' => 0, 'likes_score' => 0, 'source' => CHECK_SOURCE, 'filial' => CHECK_NOT_NULL, 'user' => CHECK_EXIST, 'comments' => CHECK_NOT_NULL, 'official_answer' => null, 'additional_data' => [ 'is_liked' => false, 'is_subscribed_to_comments' => false ], 'url' => CHECK_STRING_NOT_EMPTY, 'id' => CHECK_POSITIVE ]; точное значение тип значения и его диапазон точное значение
  61. 61. // Ожидаемый результат $expected = [ 'filial_id' => $filial_id, 'user_id' => $user_id, 'text' => $text, 'date_created' => CHECK_DATETIME_FORMAT, 'date_edited' => null, 'comments_count' => 0, 'likes_score' => 0, 'source' => CHECK_SOURCE, 'filial' => CHECK_NOT_NULL, 'user' => CHECK_EXIST, 'comments' => CHECK_NOT_NULL, 'official_answer' => null, 'additional_data' => [ 'is_liked' => false, 'is_subscribed_to_comments' => false ], 'url' => CHECK_STRING_NOT_EMPTY, 'id' => CHECK_POSITIVE ]; точное значение enum тип значения и его диапазон точное значение
  62. 62. // Ожидаемый результат $expected = [ 'filial_id' => $filial_id, 'user_id' => $user_id, 'text' => $text, 'date_created' => CHECK_DATETIME_FORMAT, 'date_edited' => null, 'comments_count' => 0, 'likes_score' => 0, 'source' => CHECK_SOURCE, 'filial' => CHECK_NOT_NULL, 'user' => CHECK_EXIST, 'comments' => CHECK_NOT_NULL, 'official_answer' => null, 'additional_data' => [ 'is_liked' => false, 'is_subscribed_to_comments' => false ], 'url' => CHECK_STRING_NOT_EMPTY, 'id' => CHECK_POSITIVE ]; формат значения точное значение enum тип значения и его диапазон точное значение
  63. 63. // Ожидаемый результат $expected = [ 'filial_id' => $filial_id, 'user_id' => $user_id, 'text' => $text, 'date_created' => CHECK_DATETIME_FORMAT, 'date_edited' => null, 'comments_count' => 0, 'likes_score' => 0, 'source' => CHECK_SOURCE, 'filial' => CHECK_NOT_NULL, 'user' => CHECK_EXIST, 'comments' => CHECK_NOT_NULL, 'official_answer' => null, 'additional_data' => [ 'is_liked' => false, 'is_subscribed_to_comments' => false ], 'url' => CHECK_STRING_NOT_EMPTY, 'id' => CHECK_POSITIVE ]; формат значения точное значение enum тип значения и его диапазон наличие атрибута точное значение
  64. 64. Примеры
  65. 65. public function providerGetBlogAuthors() { // запись блога $article = Db()->table('articles')->isPublished(true)->hasAuthor(true)->getRow(); // неопубликованная запись блога $article_not_published = Db()->table('articles')->isPublished(false)->getRow(); return [ // опубликованная [$article, Config()->roles->user, 200], // неопубликованная под авторизованным юзером [$article_not_published, Config()->roles->user, 404], // неопубликованная под гостем [$article_not_published, Config()->roles->guest, 404], ]; } public function testGetBlogAuthors($article, $user_id, $expected_code) Формируем тестовые наборы
  66. 66. /** * @dataProvider providerGetBlogAuthors */ public function testGetBlogAuthors($article, $user_id, $expected_code) { // Задаём http-метод, метод API, параметры запроса $this->http_method = 'GET'; $this->method = "blogs/{$article->id}/authors"; // Выполняем запрос и проверяем коды ответа $this->asUser($user_id)->send(); $this->waitFor($expected_code); Получаем авторов записи блога
  67. 67. // проверяем поля в ответе if ($expected_code === 200) { $actual = $this->getResponseBody()->authors; // авторов может быть много, проверяем первого $expected[0] = [ 'name' => CHECK_STRING_NOT_EMPTY, 'user' => CHECK_NOT_NULL, ]; $this->assert($expected, $actual); } Получаем авторов записи блога
  68. 68. public function testDeleteReview($entity_id, $cause, $user_id, $expected_code) { // Задаём http-метод, метод API, параметры запроса $this->http_method = 'DELETE'; $this->method = "reviews/{$entity_id}"; $this->params = [ 'cause' => $cause ]; // Выполняем запрос и проверяем коды ответа $this->asUser($user_id)->send(); $this->waitFor($expected_code); // Проверка на повторное удаление сущности if ($expected_code === 202) { $this->asUser($user_id)->send(); $this->waitFor(404); } } Удаляем отзыв
  69. 69. Запуск и отладка
  70. 70. Запуск и отладка
  71. 71. Приёмочное тестирование фичи 10-20 сек
  72. 72. Регрессия 1400 тестов 35 000 проверок 5 минут 1400 тестов
  73. 73. Регрессия 1400 тестов 35 000 проверок 5 минут 1400 тестов 28 860 минут вместо
  74. 74. Процессы ● отдельный репозиторий ● все пушат, мёржат, коммитят ● новая фича покрывается при тестировании, или даже параллельно с разработкой :)
  75. 75. Flamp API JSON-schema Flamposcope 2fingers Луковица качества API
  76. 76. Вопросы?

×