Автор статьи: Владимир Васяев Блог компании Maxilect Недавно я сменил проект — пришел в новую разработку, где до меня не было никакого тестирования, ни ручного, ни автоматического. Условий на инструментарий (за исключением того, что это Python) заказчик не накладывал, так что я сделал собственный выбор. В этой статье я расскажу, почему в таких условиях предпочел Robot Framework. А в конце будет немного специально написанных под статью примеров, иллюстрирующих, о чем речь.
Автоматизацией тестирования я занимаюсь уже более 10 лет, а с Robot Framework взаимодействовал порядка трех из них.
Как я отметил выше, не так давно я пришел на новый проект, где начал автоматизацию тестирования с нуля. Подробностей о проекте рассказать не могу — NDA. Отмечу лишь, что это крутой инструмент автоматизации, который в перспективе должен сэкономить массу человеческих ресурсов. Построен он из микросервисов. Пока что моя работа касается четырех из них, но в будущем я расширю свою деятельность и на другие — все упирается в то, что у меня лишь 8 рабочих часов в сутки. На все нужно время.
Отсутствие тестирования в случае этого проекта было тождественно отсутствию рекомендуемого инструментария. В рамках стека Python у меня была свобода выбора. И я ей воспользовался.
Почему Robot Framework?Думаю, меня можно отнести к поклонникам Robot Framework. Как мне кажется, на нем можно сделать практически все.
На других проектах в компании используется pytest. Однако, на мой взгляд, он не имеет таких возможностей, поскольку ограничен возможностями Python. Ты просто пишешь код на Python и отмечаешь, что в такой-то переменной ожидаешь определенное значение. На мой вкус, это просто, но слишком абстрактно. Очень многое передано на откуп разработчику, которому все полезные фишки придется добавлять руками. Robot Framework делает это сам. Вот четыре момента, которые стоят того, чтобы взять его к себе на проект.
ГибкостьRobot Framework сам написан на Python (т.е. может все, что может Python), и для него есть множество библиотек, созданных сообществом, что многократно расширяет спектр решаемых проблем.
Под Robot Framework можно легко писать свои библиотеки, интегрировав его практически с чем угодно, и все будет сразу работать. Не потребуется никаких костылей и велосипедов.
А кроме того, Robot Framework позволяет писать параметрические тесты (шаблоны), что сильно ускоряет работу автоматизатора.
Русский язык в тестахОдин из принципиальных моментов — то, что keyword (аналоги обычных методов в Robot Framework, которые исторически почему-то называют именно так) можно писать по-русски. В результате тексты тестов, конечно, далеки от литературного языка, но с первого взгляда становится понятно, что там происходит. Нужно это в первую очередь не конечным разработчикам, а, например, стейкхолдерам. Они могут самостоятельно открывать тесты и видеть, что именно там происходит: “Выбрать случайный элемент из…” и т.п.
ТегированиеRobot Framework хорошо работает с тегами. Можно не только запускать тесты с определенным тегом, но и анализировать с их помощью отчеты. Лог-файл содержит статистику по тегам, на основе которой можно предпринимать какие-то действия, если продумать расстановку тегов заранее.
Удобно, что не нужно в каждом тесте писать все теги в длинную портянку, а можно вложить тесты в древовидную структуру. В моем случае это выглядит так: первый уровень — микросервис, второй — вид теста, третий — сами тесты (удобно положить init-файл, содержащий теги для того, что вложено внутрь).
У себя я создал тег для каждого микросервиса, каждого тестового подхода (smoke-тесты, CRUD-тесты, интеграция и т.п.). Могу запустить тесты конкретного вида или только для конкретного сервиса. Также я тегирую вид функции — списковые или детализированные. И если в продукте “ломается” функционал, отвечающий за списковые функции, все тесты с этим тегом станут красными, несмотря на то, где они расположены и к чему относятся.
Тегами я осуществляю привязку автотестов к Jira. Когда баг из трекера закрывается, а тест из красного становится зеленым, у нас остается история, что именно изменилось. Спустя много месяцев, если тест снова станет красным, мы сможем увидеть, какие действия были предприняты для исправления проблемы в прошлый раз, и даже предположить, что привело к повторному воспроизведению бага.
Еще один специальный тег у меня добавлен для некритичных багов. GitLab не дает нам ничего делать со сборкой, если падает хотя бы один тест. Это логично — пока все баги не исправятся, мы не можем выпустить продукт или даже недельный спринт. Но низкоприоритетные и незначимые баги есть и у нас. Для них-то я и выделил тег, позволяющий Robot Framework не ронять всю сборку, если конкретно эти тесты (тесты с этим тегом) не проходят.
Отличный логАнализ логов — неотъемлемая часть тестирования. Что бы ни происходило в момент выполнения тестов, Robot Framework пишет абсолютно все. Вплоть до того, что мне пришлось писать специальную обертку, которая скрывает из лога логин и пароль для подключения к базе.
Такая детализация помогает намного быстрее понять, в чем причина падения теста — тестируемая система неправильно работает или в тесте что-то не учтено и его нужно исправить? Ответ на этот вопрос не всегда очевиден. Ошибки разработчиков и тестировщиков распределяются 50/50.
В других инструментах — в том же pytest — можно формировать такой же подробный лог. Но там задача его генерации ложится на плечи разработчика, который пишет тесты. Ему нужно продумать, какие записи в логе действительно нужны.
Что есть на проекте уже сейчасС того момента, как я начал использовать Robot Framework, прошло уже несколько месяцев. На данный момент на Robot Framework реализовано более 200 тестов и, как было упомянуто выше, есть интеграция с GitLab, которая помогает проверять вносимые в разрабатываемый продукт изменения (тест-кейсы с идентификаторами, позволяющими привязать к ним автотесты, хранятся в testrail).
Для расчета покрытия я написал утилиту, которая берет из Swagger список API бэкенда и сопоставляет его с тем, что было протестировано. Таким образом, у нас есть четкое понимание покрытия в текущий момент времени. К примеру, мы знаем, что сегодня этот микросервис покрыт целиком, а другой — на 98%. А после добавления новой функциональности, которая еще не отражена в тестировании, покрытие падает. Соответственно, можно спланировать, чтобы на следующий спринт я реализовал автотесты.
Естественно, проект развивается дальше. Например, сейчас мы разворачиваем мок-сервер (о том, что это такое и зачем нужно, мой коллега уже писал на Хабре).
А теперь к практикеПриведенные примеры созданы специально для статьи, чтобы проиллюстрировать изложенные выше идеи. Если вы захотите поэкспериментировать с этими примерами собственноручно, все они выложены в нашем репозитории.
Пример самого простого тестаНачнем с самого простого теста на Robot Framework.
В примере ниже сначала создается сессия до некого заведомо доступного ресурса (в нашем случае — en.wikipedia.org/wiki). Вызывая Get корня (/), мы проверяем код состояния 200.
*** Settings ***
Documentation Пример smoke-автотеста.
Library RequestsLibrary
*** Variables ***
${base_url} https://en.wikipedia.org/wiki
${url} /
*** Test Cases ***
Проверить доступность Wiki
Create session conn ${base_url} disable_warnings=1
${response} Get request conn ${url}
Delete all sessions
Should be equal ${response.status_code} ${200}
Шаблонные (параметрические) тесты Robot Framework позволяет создавать шаблоны тестов. Если мы переведем тест из предыдущего примера в шаблонный вид, то сможем его легко масштабировать, добавляя вызовы с нужными аргументами. Для примера проверим ответ 200 для страниц с биографиями ученых.
*** Settings ***
Documentation Пример smoke-автотеста. С использованием шаблона теста.
... Шаблонные тесты легко масштабируются.
Library RequestsLibrary
Test Setup Создать соединение
Test Teardown Закрыть все соединения
Test Template Smoke-тест
*** Variables ***
${base_url} https://en.wikipedia.org/wiki
*** Test Cases ***
Проверить доступность страницы о Ньютоне
/Isaac_Newton
Проверить доступность страницы об Эйнштейне
/Albert_Einstein
Проверить доступность страницы о Хокинге
/Stephen_Hawking
*** Keywords ***
Создать соединение
Create session conn ${base_url} disable_warnings=1
Закрыть все соединения
Delete all sessions
Smoke-тест
[Arguments] ${url}
${response} Get request conn ${url}
Should be equal ${response.status_code} ${200}
Тегирование и чтение логовПродолжаем совершенствовать простейший тест.
Имея шаблон как единую точку входа, мы можем легко добавить проверку года рождения ученого. Так мы подтверждаем, что загруженная страница отображает правильные данные.
Кроме того, в примере ниже я обернул существующие keyword в русские названия – на мой вкус, так тест читается органичнее. Меня как тестировщика всегда раздражали методы, названные «как бы по-английски», но совершенно безграмотно. Всегда лучше писать на том языке, который знаешь.
*** Settings ***
Documentation Пример smoke-автотеста. С использованием шаблона теста.
... Шаблонные тесты легко масштабируются.
... Шаблонные тесты так же легко расширяются.
... Добавлено тэгирование.
Library RequestsLibrary
Test Setup Создать соединение
Test Teardown Закрыть все соединения
Test Template Smoke-тест
*** Variables ***
${base_url} https://en.wikipedia.org/wiki
*** Test Cases ***
Проверить доступность страницы о Ньютоне
[Tags] Newton
/Isaac_Newton 1642
Проверить доступность страницы об Эйнштейне
[Tags] Einstein
/Albert_Einstein 1879
Проверить доступность страницы о Хокинге
[Tags] Hawking
/Stephen_Hawking 1942
Проверить доступность несуществующей страницы (для отображения ошибки)
[Tags] Numbers
/123456789 1899
*** Keywords ***
Создать соединение
Create session conn ${base_url} disable_warnings=1
Закрыть все соединения
Delete all sessions
Smoke-тест
[Arguments] ${url} ${expected_word}
${response} Get request conn ${url}
Should be equal ${response.status_code} ${200}
... msg=При выполнении GET ${url} был получен код состояния, отличный от 200 ОК.
Проверить наличие слова на странице ${response.text} ${expected_word}
Проверить наличие слова на странице
[Arguments] ${text} ${expected_word}
Should contain ${text} ${expected_word} msg=Не найдено искомое слово ${expected_word}!
Обратите внимание на [Tags] . Сюда можно внести теги, которые, как я писал выше, помогут оценить проблемы на уровне отчета. Аналогично Force Tags в файле __init__.robot (см. пример в нашем репозитории) позволяет задать теги для всех тестов в директории, включая вложенные. Если тегирование выполнено правильно, даже не читая названий самих тестов и не залезая в их логику, можно довольно точно предположить, что именно не работает в тестируемом проекте.
Посмотрите на отчет о запуске этих тестов. Для наглядности я добавил тест, который будет находить ошибку.
Статистика отчета — его самая важная часть.
В нашем примере полностью не прошли тесты с тегом numbers (1 из 1 у нас, а в реальной жизни будет, например 17 из 20). Можно предположить, что проблема именно в этой странице.
Теги помогают запускать тесты выборочно. Для запуска всех тестов с конкретным тегом в строке запуска необходимо указать:
--include <tag>
Поддерживаются даже логические операции с тегами:
-- include <tag>AND<tag>
Например, если нужно запустить только Smoke тест для тестов с тегом numbers, следует добавить:
--include smokeANDnumbers
Некритические тестыПерейдем к хитростям, которые здорово упрощают работу.
Разметив тест тегами, можно определить один или несколько из них как “некритичные”. Тест с таким тегом всё равно будет показывать наличие ошибок (если они есть), но в итоге эти ошибки не будут трактоваться как «недопустимые». Я пользуюсь этой опцией, когда некие минорные баги учли, занесли в баг-трекер, но пока не исправили. Без нее автотесты, включенные в CI, при обнаружении подобных известных проблем не дадут собрать проект, а это не всегда удобно.
Добавим новый тест:
Проверить доступность несуществующей страницы (для демонстрации некритичных ошибок)
[Tags] Letters Known
/abcdefghi 1799
При запуске добавленный тег определим как «некритичный», используя ключ:
--noncritical Known
Keyword на python Следующим примером я проиллюстрирую, как добавить свою библиотеку.
Создадим новый keyword “Генерировать массив чисел”. Его назначение очевидно (за это я и люблю русские названия).
from random import randint
from typing import List
from robot.api.deco import keyword
class ArrayGeneratorLibrary:
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
@keyword("Генерировать массив чисел")
def generate_array(self, length: int, minimal: int, maximal: int) -> List[int]:
result = []
for i in range(int(length)):
result.append(randint(int(minimal), int(maximal)))
return result
Подключаем библиотеку в тесте:
Library libraries.ArrayGeneratorLibrary
и используем ее:
Работа с библиотекой, написанной на python.
${array} Генерировать массив чисел ${5} ${2} ${8}
Log to console ${array}
Не забудьте, что если у вас вложенная структура, разделять ее следует точками, как в примере.
Еще небольшая хитрость: цифры, переданные в ${} , трактуются как int, а не как строки!
Встроенные аргументыЕще одна приятная вещь — встроенные аргументы, которые передаются не в конце вызова, как обычно, а прямо в его теле.
Для иллюстрации напишем обертку для созданного выше генератора массивов, позволяющую использовать встроенные аргументы:
Сгенерировать ${n} чисел, от ${from} до ${to}
${result} Генерировать массив чисел ${n} ${from} ${to}
[Return] ${result}
Теперь можно писать так:
Пример обёртки и метода со встроенными аргументами.
${array} Сгенерировать 5 чисел, от 2 до 8
Log to console ${array}
Подмена части имени метода, вставка python и циклы Следующая хитрость, которая мне очень нравится в Robot Framework, – подмена части имени. Допустим, у нас есть два метода: один выбирает четные числа, другой – нечетные.
Найти чётные числа в списке
[Arguments] ${list}
${evens} Evaluate [i for i in $list if i % 2 == 0]
[Return] ${evens}
Найти нечётные числа в списке
[Arguments] ${list}
${odds} Evaluate [i for i in $list if i % 2 != 0]
[Return] ${odds}
Использованный выше keyword Evaluate позволяет выполнить строчку кода python «прямо здесь». Обратите внимание, если вы не хотите заменить кусок строки содержимым переменной, а именно передать ссылку на нее, то следует указывать имя переменной сразу за знаком $ без фигурных скобок!
А вот так можно вызвать оба метода, подменив различающуюся часть их названия:
Пример передачи части названия метода, в виде параметра. Цикл.
${types} Create list чётные нечётные
${array} Сгенерировать 5 чисел, от 12 до 28
FOR ${type} IN @{types}
${numbers} Run keyword Найти ${type} числа в списке ${array}
log to console ${numbers}
END
Декораторы методов Да, Robot Framework позволяет написать декоратор для других keyword!
В качестве иллюстрации этой возможности напишем декоратор, который выбирает отрицательные числа в ответе любого метода, возвращающего список.
Найти отрицательные числа в ответе, вызвав
[Arguments] ${keyword} @{args} &{kwargs}
${list} Run keyword ${keyword} @{args} &{kwargs}
${negs} Evaluate [i for i in $list if i < 0]
[Return] ${negs}
Обратите внимание:
@{args} – это все неименованные аргументы;
&{kwargs} – это все именованные аргументы.
Имея эту связку, можно перенаправить их, фактически создав декоратор.
Работа с декоратором будет выглядеть так:
Пример декоратора метода
${negs} Найти отрицательные числа в ответе, вызвав Генерировать массив чисел 10 -5 5
log to console ${negs}
Вместо заключения В примерах выше я показал основные возможности Robot Framework, которые существенно облегчают жизнь. Но этим списком его фишки не ограничиваются.
Если у вас будут какие-то вопросы, пишите в комментариях. Мы выделим основное направление читательского интереса и ответим на вопросы продолжением текста. Обсудить в форуме |