Привет, Хабр! Как-то раз на нашем внутреннем семинаре мой руководитель – глава отдела тестирования – начал свою речь со слов «тестирование не нужно». В зале все притихли, некоторые даже пытались упасть со стульев. Он продолжил свою мысль: без тестирования вполне возможно создать сложный и дорогостоящий проект. И, скорее всего, он будет работать. Но представьте, насколько увереннее вы будете себя ощущать, зная, что продукт работает как надо.

В Badoo релизы происходят довольно часто. Например, серверная часть наравне с desktop web релизится дважды в день. Так что мы не понаслышке знаем, что сложное и медленное тестирование – камень преткновения разработки. Быстрое же тестирование – это счастье. Итак, сегодня я расскажу о том, как в компании Badoo устроено smoke-тестирование.

Что такое smoke-тестирование

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

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

Давайте рассмотрим простой пример. Предпродакшн нашего приложения находится по адресу bryak.com (любые совпадения с реальными сайтами случайны). Мы подготовили и залили туда новый релиз для тестирования. Что стоит проверить в первую очередь? Я бы начал с проверки того, что приложение всё ещё открывается. Если web-сервер нам отвечает «200», значит, всё хорошо и можно приступать к проверке функционала.

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

Наш первый smoke-тест

В Badoo серверная часть написана по большей части на PHP. Unit-тесты по понятным причинам пишутся на нём же. Итого у нас уже есть PHPUnit. Чтобы не плодить технологии без необходимости, мы решили писать smoke-тесты тоже на PHP. Помимо PHPUnit, нам потребуется клиентская библиотека работы с URL (libcurl) и PHP extension для работы с ней – cURL.

По сути, тесты просто делают нужные нам запросы на сервер и проверяют ответы. Всё завязано на методе getCurlResponse() и нескольких типах ассертов.

Сам метод выглядит примерно так:

Public function getCurlResponse($url, array $params = [ ‘cookies’ => , ‘post_data’ => , ‘headers’ => , ‘user_agent’ => , ‘proxy’ => , ], $follow_location = true, $expected_response = ‘200 OK’) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (isset($params[‘cookies’]) && $params[‘cookies’]) { $cookie_line = $this->prepareCookiesDataByArray($params[‘cookies’]); curl_setopt($ch, CURLOPT_COOKIE, $cookie_line); } if (isset($params[‘headers’]) && $params[‘headers’]) { curl_setopt($ch, CURLOPT_HTTPHEADER, $params[‘headers’]); } if (isset($params[‘post_data’]) && $params[‘post_data’]) { $post_line = $this->preparePostDataByArray($params[‘post_data’]); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_line); } if ($follow_location) { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); } if (isset($params[‘proxy’]) && $params[‘proxy’]) { curl_setopt($ch, CURLOPT_PROXY, $params[‘proxy’]); } if (isset($params[‘user_agent’]) && $params[‘user_agent’]) { $user_agent = $params[‘user_agent’]; } else { $user_agent = USER_AGENT_DEFAULT; } curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); curl_setopt($ch, CURLOPT_AUTOREFERER, 1); $response = curl_exec($ch); $this->logActionToDB($url, $user_agent, $params); if ($follow_location) { $this->assertTrue((bool)$response, "Empty response was received. Curl error: " . curl_error($ch) . ", errno: " . curl_errno($ch)); $this->assertServerResponseCode($response, $expected_response); } curl_close($ch); return $response; }
Сам метод умеет по заданному URL возвращать ответ сервера. На вход принимает параметры, такие как cookies, headers, user agent и прочие данные, необходимые для формирования запроса. Когда ответ от сервера получен, метод проверяет, что код ответа совпадает с ожидаемым. Если это не так, тест падает с ошибкой, сообщающей об этом. Это сделано для того, чтобы было проще определить причину падения. Если тест упадёт на каком-нибудь ассерте, сообщив нам, что на странице нет какого-то элемента, ошибка будет менее информативной, чем сообщение о том, что код ответа, например, «404» вместо ожидаемого «200».

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

Самый простой тест выглядит примерно так:

Public function testStartPage() { $url = ‘bryak.com’; $response = $this->getCurlResponse($url); $this->assertHTMLPresent("
Такой тест проходит менее чем за секунду. За это время мы проверили, что стартовая страница отвечает «200», и на ней есть элемент body. С тем же успехом мы можем проверить любое количество элементов на странице, продолжительность теста существенно не изменится.

Плюсы таких тестов:

  • скорость – тест можно запускать так часто, как это необходимо. Например, на каждое изменение кода;
  • не требуют специального софта и железа для работы;
  • их несложно писать и поддерживать;
  • они стабильные.
По поводу последнего пункта. Я имею в виду – не менее стабильные, чем сам проект.

Авторизация

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

Самый просто вариант – авторизационная cookie. Если добавить её к запросу, то сервер нас «узнает». Такую cookie можно захардкодить в тесте, если её время жизни довольно большое, а можно получать автоматически, отправляя запросы на страницу авторизации. Давайте подробнее рассмотрим второй вариант.

Нас интересует форма, куда надо ввести логин и пароль пользователя.

Открываем эту страницу в любом браузере и открываем инспектор. Вводим данные пользователя и сабмитим форму.

В инспекторе появился запрос, который нам надо имитировать в тесте. Можно посмотреть, какие данные, помимо очевидных (логин и пароль), отсылаются на сервер. Для каждого проекта по-разному: это может быть remote token, данные каких-либо cookies, полученных ранее, user agent и так далее. Каждый из этих параметров придётся предварительно получить в тесте, прежде чем сформировать запрос на авторизацию.

В инструментах разработчика любого браузера можно скопировать запрос, выбрав пункт copy as cURL. В таком виде команду можно вставить в консоль и рассматривать там. Там же её можно опробовать, поменяв или добавив параметры.

В ответ на такой запрос сервер вернёт нам cookies, которые мы будем добавлять в дальнейшие запросы, чтобы тестировать авторизованные страницы.

Поскольку авторизация – довольно долгий процесс, авторизационную cookie я предлагаю получать только один раз для каждого пользователя и сохранять где-то. У нас, например, такие cookies хранятся в массиве. Ключом является логин пользователя, а значением – информация о них. Если для следующего пользователя ключа ещё нет, авторизуемся. Если есть – делаем интересующий нас запрос сразу.

Public function testAuthPage() { $url = ‘bryak.com’; $cookies = $this->getAuthCookies(‘[email protected]’, ‘12345’); $response = $this->getCurlResponse($url, [‘cookies’ => $cookies]); $this->assertHTMLPresent("", $response, "Error: test cannot find body element on the page."); }
Как мы видим, добавился метод, который получает авторизационную cookie и просто добавляет её в дальнейший запрос. Сам метод реализуется довольно просто:

Public function getAuthCookies($email, $password) { // check if cookie already has been got If (array_key_exist($email, self::$known_cookies)) { return self::$known_cookies[$email]; } $url = self::DOMAIN_STAGING . ‘/auth_page_adds’; $post_data = [‘email’ => $email, ‘password’ => $password]; $response = $this->getCurlResponse($url, [‘post_data’ => $post_data]); $cookies = $this->parseCookiesFromResponse($response); // save cookie for further use self::$known_cookies[$email] = $cookies; return $cookies; }
Метод сначала проверяет, есть ли для данного e-mail (в вашем случаем это может быть логин или что-то ещё) уже полученная ранее авторизационная cookie. Если есть, он её возвращает. Если нет, он делает запрос на авторизационную страницу (например, bryak.com/auth_page_adds) с необходимыми параметрами: e-mail и пароль пользователя. В ответ на этот запрос сервер присылает заголовки, среди которых есть интересующие нас cookies. Выглядит это примерно так:

HTTP/1.1 200 OK Server: nginx Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: name=value; expires=Wed, 30-Nov-2016 10:06:24 GMT; Max-Age=-86400; path=/; domain=bryak.com
Из этих заголовков нам при помощи несложного регулярного выражения надо получить название cookie и её значение (в нашем примере это name=value). У нас метод, который парсит ответ, выглядит так:

$this->assertTrue((bool)preg_match_all("/Set-Cookie: (([^=]+)=([^;]+);.*)\n/", $response, $mch1), "Cannot get "cookies" from server response. Response: " . $response);
После того, как cookies получены, мы можем смело добавлять их в любой запрос, чтобы сделать его авторизованным.

Разбор падающих тестов

Из вышесказанного следует, что такой тест – это набор запросов к серверу. Делаем запрос, совершаем манипуляцию с ответом, делаем следующий запрос и так далее. В голову закрадывается мысль: если такой тест упадёт на десятом запросе, может оказаться непросто разобраться в причине его падения. Как упростить себе жизнь?

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

Ещё полезно собирать артефакты. Когда наш тест падает, он сохраняет последний ответ сервера в HTML-файлик и закидывает в хранилище артефактов, где этот файлик можно открыть из браузера, указав название теста.

Например, тест у нас упал на том, что не может найти на странице кусочек HTML:

Link
Мы заходим на наш коллектор и открываем соответствующую страницу:

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

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

Помимо помощи в разборе ошибок, логи, описанные выше, помогают нам формировать список авторизованных и неавторизованных страниц, которые мы протестировали. Глядя на него, легко искать и устранять пробелы.

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

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

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

Итоги

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

За это время мы убеждаемся, что:

  • наш проект открывается на всех языках (которых у нас более 40 на продакшене);
  • для основных стран отображаются корректные формы оплаты с соответствующим набором способов оплаты;
  • корректно работают основные запросы к API;
  • корректно работает лендинг для редиректов (в том числе и на мобильный сайт при соответствующем юзер-агенте);
  • все внутренние проекты отображаются правильно.
Тестам на Selenium WebDriver для всего этого потребовалось бы в разы больше времени и ресурсов. Добавить метки

Проводить дымовое и санитарное тестирование начинают сразу же после выхода очередной версии проекта. Для многих молодых тестировщиков этот процесс кажется абсолютным хаосом. Узнал себя? Тогда эта статья для тебя. Сейчас мы рассмотрим определения дымового и санитарного тестирования, а также покажем разницу между ними на легких для понимания примерах.

Дымовое тестирование:

Дымовое тестирование проводят для того, чтобы убедиться в пригодности полученного билда к тестированию. Его также называют проверкой “нулевого дня”.

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

Санитарное тестирование:

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

Пример для лучшего понимания разницы между дымовым и санитарным тестированием:

Есть проект, для которого запланирован первоначальный релиз. Команда разработчиков выпускает билд на тестирование, команда тестировщиков начинает работу. Самое первое тестирование — это тестирование на пригодность. Нужно выяснить, можно работать с этой версией или нет. Это и есть дымовое тестирование. Если команда дает добро на дальнейшую работу с билдом, он отправляется на более глубокие стадии тестирования. Представим, что у билда есть три модуля: “Логин”, “Админ” и “Сотрудник”. Команда тестировщиков проверяет работоспособность исключительно основных функций каждого из модулей, не углубляясь в проверку частностей. Это будет санитарное тестирование.

Еще несколько различий между дымовым и санитарным тестированием:

  • Дымовое тестирование проводится и разработчиками, и тестировщиками;
  • Санитарное тестирование проводится только тестировщиками.
  • Дымовое тестирование охватывает весь основной функционал приложения от начала до конца;
  • Санитарное тестирование проверяет только определенный компонент приложения.
  • Дымовое тестирование проходит как стабильный, так и не стабильный билд;
  • Санитарное тестирование проходит относительно стабильная версия сборки.

Кирилл Флягин, геймдизайнер, QA Lead

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

Простые ошибки могут быть фатальными для вашего сайта — особенно если Вы — SaaS (eng. Software as a Service) компания, как мы. Если пользователь заходит на Ваш сайт и не может справиться с простым заданием, таким как зарегистрироваться или сбросить свой забытый пароль, Вы рискуете потерять этого пользователя навсегда.

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

Если все же у Вас останутся вопросы — восполнить пробелы вы сможете, посетив

Smoke-тестирование первоначально было придумано, чтобы объяснить как инженеры-электротехники проверяли работает ли их прибор — включали его и если дым шел…

Подождите, как это может быть применимо к приложениям?

Важность (и рентабельность) smoke-тестов, как правило, неизвестна для менеджеров-гуманитариев и соучредителей. Систематические smoke-тесты могут рассматриваться как неотъемлемая часть для предотвращения роста вероятности взлома. Они минимизируют вероятность того, что в Вашем веб-приложении или приложении для телефона произойдет сбой — и как все мы знаем, только одна неудача и вы можете потерять клиента навсегда.

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

Smoke-тесты созданы для того, чтобы проверить основную функциональность и должны быть неотъемлемой частью Вашего процесса тестирования. Они могут включать что-то простое, типа «Могу ли я зарегистрироваться?».

Smoke-тестирование помогает убедиться, что ни одна из основных и очевидных неудач не оставлена на волю случая. Не следует проводить более глубокий тест, пока вы не выполнили smoke-тесты на 100%, потому что они очищают программное обеспечение от фундаментальных ошибок.

Шаг 1: Решите, что надо тестировать

Определите, что Ваше приложение стремится достичь. Каковы наиболее очевидные «детские» шаги, которые необходимы, чтобы в него попасть? Каковы минимальные жизненно важные требования и в какой логической последовательности Вы их перечислите?

Создайте набор тестов. Набор тестов — это сгруппированная совокупность тест-кейсов (тестовых случаев), связанная определенным образом (например, по функциональности).

Smoke-тестирование не будет включать в себя переменные или вопросы вида «что если?». Оно предполагает только ответы да/нет, но прежде чем переходить к более подробному тестированию, все тест-кейсы должны быть пройдены с положительным результатом.

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

  1. зарегистрироваться.
  2. создать Имя пользователя.
  3. загрузить фото на аватарку.
  4. писать сообщения.
  5. отвечать на сообщения.

Шаг 2: Записываем результаты в таблицу

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

  • Пройдено: все работает идеально.
  • Частично: изначально вы можете не понимать, что некоторые действия могут быть еще подразделены, и поэтому одна часть работает, а другая — нет.
  • Провалено: не работает.

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

Шаг 3: Автоматизируем smoke-тесты

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

Не прекращай smoke-тестирование. Никогда.

Когда Ваш набор тест-кейсов для smoke-тестирования завершается с успешным исходом на 100%, подумайте об их автоматизации . Рекомендуемая частота проведения smoke-тестов — каждый день, если Ваша компания занимается разработкой каждый день.

Минимальная необходимость — проводите прогон smoke-тестов перед каждым релизом и после каждого патча.

Эмпирическое правило для smoke-теста:

  • Минимальное время: 30 минут.
  • Максимальное время: 60 минут.

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

  • Тестирование веб-сервисов ,
  • Тестирование мобильных приложений
  • Привет, Хабр! Как-то раз на нашем внутреннем семинаре мой руководитель – глава отдела тестирования – начал свою речь со слов «тестирование не нужно». В зале все притихли, некоторые даже пытались упасть со стульев. Он продолжил свою мысль: без тестирования вполне возможно создать сложный и дорогостоящий проект. И, скорее всего, он будет работать. Но представьте, насколько увереннее вы будете себя ощущать, зная, что продукт работает как надо.

    В Badoo релизы происходят довольно часто. Например, серверная часть наравне с desktop web релизится дважды в день. Так что мы не понаслышке знаем, что сложное и медленное тестирование – камень преткновения разработки. Быстрое же тестирование – это счастье. Итак, сегодня я расскажу о том, как в компании Badoo устроено smoke-тестирование.

    Что такое smoke-тестирование

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

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

    Давайте рассмотрим простой пример. Предпродакшн нашего приложения находится по адресу bryak.com (любые совпадения с реальными сайтами случайны). Мы подготовили и залили туда новый релиз для тестирования. Что стоит проверить в первую очередь? Я бы начал с проверки того, что приложение всё ещё открывается. Если web-сервер нам отвечает «200», значит, всё хорошо и можно приступать к проверке функционала.

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

    Наш первый smoke-тест

    В Badoo серверная часть написана по большей части на PHP. Unit-тесты по понятным причинам пишутся на нём же. Итого у нас уже есть PHPUnit. Чтобы не плодить технологии без необходимости, мы решили писать smoke-тесты тоже на PHP. Помимо PHPUnit, нам потребуется клиентская библиотека работы с URL (libcurl) и PHP extension для работы с ней – cURL.

    По сути, тесты просто делают нужные нам запросы на сервер и проверяют ответы. Всё завязано на методе getCurlResponse() и нескольких типах ассертов.

    Сам метод выглядит примерно так:

    Public function getCurlResponse($url, array $params = [ ‘cookies’ => , ‘post_data’ => , ‘headers’ => , ‘user_agent’ => , ‘proxy’ => , ], $follow_location = true, $expected_response = ‘200 OK’) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (isset($params[‘cookies’]) && $params[‘cookies’]) { $cookie_line = $this->prepareCookiesDataByArray($params[‘cookies’]); curl_setopt($ch, CURLOPT_COOKIE, $cookie_line); } if (isset($params[‘headers’]) && $params[‘headers’]) { curl_setopt($ch, CURLOPT_HTTPHEADER, $params[‘headers’]); } if (isset($params[‘post_data’]) && $params[‘post_data’]) { $post_line = $this->preparePostDataByArray($params[‘post_data’]); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_line); } if ($follow_location) { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); } if (isset($params[‘proxy’]) && $params[‘proxy’]) { curl_setopt($ch, CURLOPT_PROXY, $params[‘proxy’]); } if (isset($params[‘user_agent’]) && $params[‘user_agent’]) { $user_agent = $params[‘user_agent’]; } else { $user_agent = USER_AGENT_DEFAULT; } curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); curl_setopt($ch, CURLOPT_AUTOREFERER, 1); $response = curl_exec($ch); $this->logActionToDB($url, $user_agent, $params); if ($follow_location) { $this->assertTrue((bool)$response, "Empty response was received. Curl error: " . curl_error($ch) . ", errno: " . curl_errno($ch)); $this->assertServerResponseCode($response, $expected_response); } curl_close($ch); return $response; }
    Сам метод умеет по заданному URL возвращать ответ сервера. На вход принимает параметры, такие как cookies, headers, user agent и прочие данные, необходимые для формирования запроса. Когда ответ от сервера получен, метод проверяет, что код ответа совпадает с ожидаемым. Если это не так, тест падает с ошибкой, сообщающей об этом. Это сделано для того, чтобы было проще определить причину падения. Если тест упадёт на каком-нибудь ассерте, сообщив нам, что на странице нет какого-то элемента, ошибка будет менее информативной, чем сообщение о том, что код ответа, например, «404» вместо ожидаемого «200».

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

    Самый простой тест выглядит примерно так:

    Public function testStartPage() { $url = ‘bryak.com’; $response = $this->getCurlResponse($url); $this->assertHTMLPresent("
    Такой тест проходит менее чем за секунду. За это время мы проверили, что стартовая страница отвечает «200», и на ней есть элемент body. С тем же успехом мы можем проверить любое количество элементов на странице, продолжительность теста существенно не изменится.

    Плюсы таких тестов:

    • скорость – тест можно запускать так часто, как это необходимо. Например, на каждое изменение кода;
    • не требуют специального софта и железа для работы;
    • их несложно писать и поддерживать;
    • они стабильные.
    По поводу последнего пункта. Я имею в виду – не менее стабильные, чем сам проект.

    Авторизация

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

    Самый просто вариант – авторизационная cookie. Если добавить её к запросу, то сервер нас «узнает». Такую cookie можно захардкодить в тесте, если её время жизни довольно большое, а можно получать автоматически, отправляя запросы на страницу авторизации. Давайте подробнее рассмотрим второй вариант.

    Нас интересует форма, куда надо ввести логин и пароль пользователя.

    Открываем эту страницу в любом браузере и открываем инспектор. Вводим данные пользователя и сабмитим форму.

    В инспекторе появился запрос, который нам надо имитировать в тесте. Можно посмотреть, какие данные, помимо очевидных (логин и пароль), отсылаются на сервер. Для каждого проекта по-разному: это может быть remote token, данные каких-либо cookies, полученных ранее, user agent и так далее. Каждый из этих параметров придётся предварительно получить в тесте, прежде чем сформировать запрос на авторизацию.

    В инструментах разработчика любого браузера можно скопировать запрос, выбрав пункт copy as cURL. В таком виде команду можно вставить в консоль и рассматривать там. Там же её можно опробовать, поменяв или добавив параметры.

    В ответ на такой запрос сервер вернёт нам cookies, которые мы будем добавлять в дальнейшие запросы, чтобы тестировать авторизованные страницы.

    Поскольку авторизация – довольно долгий процесс, авторизационную cookie я предлагаю получать только один раз для каждого пользователя и сохранять где-то. У нас, например, такие cookies хранятся в массиве. Ключом является логин пользователя, а значением – информация о них. Если для следующего пользователя ключа ещё нет, авторизуемся. Если есть – делаем интересующий нас запрос сразу.

    Public function testAuthPage() { $url = ‘bryak.com’; $cookies = $this->getAuthCookies(‘[email protected]’, ‘12345’); $response = $this->getCurlResponse($url, [‘cookies’ => $cookies]); $this->assertHTMLPresent("", $response, "Error: test cannot find body element on the page."); }
    Как мы видим, добавился метод, который получает авторизационную cookie и просто добавляет её в дальнейший запрос. Сам метод реализуется довольно просто:

    Public function getAuthCookies($email, $password) { // check if cookie already has been got If (array_key_exist($email, self::$known_cookies)) { return self::$known_cookies[$email]; } $url = self::DOMAIN_STAGING . ‘/auth_page_adds’; $post_data = [‘email’ => $email, ‘password’ => $password]; $response = $this->getCurlResponse($url, [‘post_data’ => $post_data]); $cookies = $this->parseCookiesFromResponse($response); // save cookie for further use self::$known_cookies[$email] = $cookies; return $cookies; }
    Метод сначала проверяет, есть ли для данного e-mail (в вашем случаем это может быть логин или что-то ещё) уже полученная ранее авторизационная cookie. Если есть, он её возвращает. Если нет, он делает запрос на авторизационную страницу (например, bryak.com/auth_page_adds) с необходимыми параметрами: e-mail и пароль пользователя. В ответ на этот запрос сервер присылает заголовки, среди которых есть интересующие нас cookies. Выглядит это примерно так:

    HTTP/1.1 200 OK Server: nginx Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: name=value; expires=Wed, 30-Nov-2016 10:06:24 GMT; Max-Age=-86400; path=/; domain=bryak.com
    Из этих заголовков нам при помощи несложного регулярного выражения надо получить название cookie и её значение (в нашем примере это name=value). У нас метод, который парсит ответ, выглядит так:

    $this->assertTrue((bool)preg_match_all("/Set-Cookie: (([^=]+)=([^;]+);.*)\n/", $response, $mch1), "Cannot get "cookies" from server response. Response: " . $response);
    После того, как cookies получены, мы можем смело добавлять их в любой запрос, чтобы сделать его авторизованным.

    Разбор падающих тестов

    Из вышесказанного следует, что такой тест – это набор запросов к серверу. Делаем запрос, совершаем манипуляцию с ответом, делаем следующий запрос и так далее. В голову закрадывается мысль: если такой тест упадёт на десятом запросе, может оказаться непросто разобраться в причине его падения. Как упростить себе жизнь?

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

    Ещё полезно собирать артефакты. Когда наш тест падает, он сохраняет последний ответ сервера в HTML-файлик и закидывает в хранилище артефактов, где этот файлик можно открыть из браузера, указав название теста.

    Например, тест у нас упал на том, что не может найти на странице кусочек HTML:

    Link
    Мы заходим на наш коллектор и открываем соответствующую страницу:

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

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

    Помимо помощи в разборе ошибок, логи, описанные выше, помогают нам формировать список авторизованных и неавторизованных страниц, которые мы протестировали. Глядя на него, легко искать и устранять пробелы.

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

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

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

    Итоги

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

    За это время мы убеждаемся, что:

    • наш проект открывается на всех языках (которых у нас более 40 на продакшене);
    • для основных стран отображаются корректные формы оплаты с соответствующим набором способов оплаты;
    • корректно работают основные запросы к API;
    • корректно работает лендинг для редиректов (в том числе и на мобильный сайт при соответствующем юзер-агенте);
    • все внутренние проекты отображаются правильно.
    Тестам на Selenium WebDriver для всего этого потребовалось бы в разы больше времени и ресурсов.
  • smoke
  • Добавить метки