Почему для нового проекта я взял Robot Framework

    Недавно я сменил проект — пришел в новую разработку, где до меня не было никакого тестирования, ни ручного, ни автоматического. Условий на инструментарий (за исключением того, что это Python) заказчик не накладывал, так что я сделал собственный выбор. В этой статье я расскажу, почему в таких условиях предпочел Robot Framework. А в конце будет немного специально написанных под статью примеров, иллюстрирующих, о чем речь.

    image

    Автоматизацией тестирования я занимаюсь уже более 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 (см. пример в нашем репозитории) позволяет задать теги для всех тестов в директории, включая вложенные. Если тегирование выполнено правильно, даже не читая названий самих тестов и не залезая в их логику, можно довольно точно предположить, что именно не работает в тестируемом проекте.

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

    Статистика отчета — его самая важная часть.

    image

    В нашем примере полностью не прошли тесты с тегом 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, которые существенно облегчают жизнь. Но этим списком его фишки не ограничиваются.

    Если у вас будут какие-то вопросы, пишите в комментариях. Мы выделим основное направление читательского интереса и ответим на вопросы продолжением текста.

    Автор статьи: Владимир Васяев.

    P.S. Мы публикуем наши статьи на нескольких площадках Рунета. Подписывайтесь на наши страницы в VK, FB или Telegram-канал, чтобы узнавать обо всех наших публикациях и других новостях компании Maxilect.
    Maxilect
    0,00
    Умные решения для вашего бизнеса
    Поделиться публикацией

    Комментарии 7

      +1
      А как выполнена интеграция с Jira?
      Идентификатор Issue становится тэгом?
        0
        Feature.
          0
          Да, для интеграции тег соответствует Jira Issue ID. Кроме того, Robot Framework надо запускать в ключиком, чтобы из лога можно было напрямую перейти в Jira.
          Допустим у Вашего проекта в Jira префикс RF, то есть баг у Вас с идентификатором RF-123456. Тогда Вы привязываете к тесту тэг RF-123456, а в строке запуска робота пишете

          tagstatlink RF-*:https://ваш-хост-с-jira/browse/RF-%1:Open_in_Jira

          и все тэги, начинающиеся с RF- получат ссылки в Jira (из моего любимого лога).
          0

          А какие инструменты вы используете при работе?
          Мы попробовали робот чуть меньше года назад и ушли на pytest с чистой совестью. Интеграция с PyCharm через энное место и стабильно крашится, дебажить неудобно.

            0
            При написании тестов удобно пользоваться и PyCharm, и Atom с соответствующими плагинами. Некоторые не гнушаются даже стандартным Блокнотом. Дебаг в общепринятом смысле тут недоступен — да. Но как мы уже писали в статье, у робота прекрасный лог и его вполне достаточно для тех нужд, для которых обычно используется дебаг.
              0

              Наша команда сравнивала pytest и робот параллельно, дабы постепенно перейти с цирка на баш+c#+python+java. И вот как раз по поводу лога, всех сильно удивило, что робот не умеет показывать строку, где тест упал. Ну то есть можно подключить стектрейс руками, но очень странно, что это issue на гитхабе до сих пор висит открытым.


              Для расчета покрытия я написал утилиту, которая берет из Swagger список API бэкенда и сопоставляет его с тем, что было протестировано.

              Интересно было бы почитать про это. Просто сравнение по вызываемым эндпойнтам или какие-то тонкости?

                0
                Если вкратце, то в автотестах есть метод, который логирует каждый вызов API — URL, параметры и теги, с которыми был запущен тест, совершивший этот вызов. После выполнения тестов вручную (или силами CI) запускается утилита для анализа лога.
                Утилите указываются все теги, для которых рассчитывается покрытие (например, smoke), а она выбирает из файла записи, соответствующие указанным тэгам.
                Затем она идёт по нужному адресу в Swagger и вытягивает оттуда информацию об «энд-поинтах», методах, и параметрах — ну и сопоставляет эти данные, выводя статистику относительно всех эндпоинтов и методов.
                В файле лога большинство вызовов содержат какие-то идентификаторы и на самом деле их сложно сопоставить с тем, что лежит в Swagger. Поэтому в утилиту заложена хитрая логика, которая умеет выделять идентификаторы из URL и делать на их основе пометки, впоследствии используемые при сопоставлении.
                Помимо развёрнутой информации (что покрыто, что не покрыто, что покрыто частично — когда использованы не все параметры) она генерит ещё и svg-плашку, которая выводится в описании проекта gitlab.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое