Вступление
Задача по автоматизации тестирования не нова, но тем не менее имеющиеся средства в области тестирования web приложений могут иметь свои ограничения.
Что, если у нас под сотню различных web приложений, которые необходимо обновить в течение короткого периода времени, а тестов на проверку их работоспособности нет? Разработка UI тестов потребует много времени, а просто сделать запрос curl и проверить что вернулся 200 OK, недостаточно.
Нужен разумный компромисс, простое, но в тоже время достаточное универсальное средство по разработке автоматических тестов. Так на свет появился SWAT.
Основания идея
Итак, SWAT с точки зрения конечного пользователя — консольный клиент для запуска тестовых сценариев и некоторый язык их написания (DSL)
Пользователь создает тестовые сценарии в определенном формате, помещает их в отдельную директорию, и запускает. В общем случае все это выглядит так:
$ swat /path/to/your/project/ $base_url
Таким образом, SWAT проект — это папка с тестовыми сценариями, а также некий базовый URL, в который будут посылаться все http запросы при тестирование web приложения. Окей, пока ничего нового, во многих системах тестирования используются подобные лейауты… В чем же суть системы SWAT?
На секунду забудем, что мы говорим о SWAT и представим себе, что все что мы можем сделать это выполнить обычный http запрос с помощью утилиты curl и проанализировать ответ с помощью утилиты grep:
$ curl $base_url | grep foo-bar-baz
Собственно это и есть квинтэссенция фреймворка SWAT.
Вся соль в том что ответы от тестируемого сервера воспринимаются просто как текст, в котором можно выполнить разнообразный поиск плюс добавлена проверка успешного http статуса. Как показал моя практика этих двух методов ( грубо говоря проверить, что вернулось 200 OK и посмотреть что вернулось ) может быть вполне достаточно для написания тестовых сценариев различной степени сложности от поверхностных smoke тестов до полноценных функциональных
Как показал мой практический опыт применения — SWAT способен на многое, читаем дальше.
Описание DSL и структуры данных
SWAT базируется на наборе договоренностей с точки зрения именования файлов и директорий. А также предоставляет специальный синтаксис для написания правил валидации произвольного текста — в нашем случае ответа от сервера.
Начнём с описания файловой структуры типового SWAT проекта.
Итак, SWAT проект, как уже упоминалось ранее, это просто директория с файлами и поддиректориями, описывающими тестовую логику.
Что-же, все что нужно сделать сначала — это просто создать проект:
$ mkdir swat-project
Введу пару терминов для упрощения понимания материала.
Имея дело с тестированием любого web приложения можно говорить о доступных в нем ресурсах или попросту маршрутах, к которым можно сделать запрос с помощью различных http методов
Итак, допустим, тестируемое нами приложение имеет следующий набор ресурсов и методов доступа к ним:
GET / # корневой запрос
GET foo/bar # GET запрос в ресурс foo/bar
POST bar/baz # POST запрос в ресурс bar/baz
Что бы описать указанную конфигурацию с помощью SWAT нужно совсем немного — использовать договорённость, принятую в SWAT проектах, а именно — ресурсы — это просто директории, а методы — это файлы. Собственно выглядеть это будет так:
$ cd swat-project
$ mkdir -p foo/bar
$ mkdir -p bar/baz
$ touch get.txt
$ touch foo/bar/get.txt
$ touch bar/baz/post.txt
# И так далее ...
Надеюсь, принцип понятен. Имена директорий соответствуют именам http ресурсов, а имена файлов — названиям http методов. Почему имена файлов методов содержат расширение *.txt станет очевидно чуть позже, сейчас на это не обращаем внимания.
Поздравляю! Мы только что написали минимальный набор тестов, который можно запустить ( при условии конечно, что у нас есть web сервис, который будет принимать запросы ):
$ cd swa-project
$ swat ./ 127.0.0.1:3000
/home/vagrant/.swat/.cache/31999/prove/00.GET.t ...........
ok 1 - GET 127.0.0.1:3000/ succeeded
# response saved to /home/vagrant/.swat/.cache/31999/prove/KBoHRGrYRm
1..1
ok
/home/vagrant/.swat/.cache/31999/prove/bar/baz/00.POST.t ..
ok 1 - POST 127.0.0.1:3000/bar/baz succeeded
# response saved to /home/vagrant/.swat/.cache/31999/prove/z5lXw_dCLa
1..1
ok
/home/vagrant/.swat/.cache/31999/prove/foo/bar/00.GET.t ...
ok 1 - GET 127.0.0.1:3000/foo/bar succeeded
# response saved to /home/vagrant/.swat/.cache/31999/prove/_DnvkvcUBw
1..1
ok
All tests successful.
Files=3, Tests=3, 0 wallclock secs ( 0.04 usr 0.02 sys + 0.25 cusr 0.03 csys = 0.34 CPU)
Result: PASS
Как мы видим, SWAT успешно распарсил файловую структуру, превратит её в набор http запросов, и выполнил их. При этом по умолчанию ответы от сервера, были провалидированы на наличие успешного http статуса и в случае ошибок со стороны сервера такие тесты оказались бы не пройденными:
$ cd swat-project
# создаем неизвестный маршрут:
$ mkdir unknown
$ touch unknown/get.txt
# снова запускаем SWAT тесты:
$ swat ./ 127.0.0.1:3000
/home/vagrant/.swat/.cache/32379/prove/bar/baz/00.POST.t ..
ok 1 - POST 127.0.0.1:3000/bar/baz succeeded
# response saved to /home/vagrant/.swat/.cache/32379/prove/Um9VB1zVyS
1..1
ok
/home/vagrant/.swat/.cache/32379/prove/unknown/00.GET.t ...
not ok 1 - GET 127.0.0.1:3000/unknown succeeded
# Failed test 'GET 127.0.0.1:3000/unknown succeeded'
# at /usr/local/share/perl/5.20.2/swat.pm line 81.
# curl -X GET -k --connect-timeout 20 -m 20 -L -f -i -o /home/vagrant/.swat/.cache/32379/prove/TJO6JpsClL --stderr /home/vagrant/.swat/.cache/32379/prove/TJO6JpsClL.stderr '127.0.0.1:3000/unknown'
# % Total % Received % Xferd Average Speed Time Time Time Current
# Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (22) The requested URL returned error: 404 Not Found
# can't continue here due to unsuccessfull http status code
1..1
# Looks like you failed 1 test of 1.
# Looks like your test exited with 1 just after 1.
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/1 subtests
/home/vagrant/.swat/.cache/32379/prove/foo/bar/00.GET.t ...
ok 1 - GET 127.0.0.1:3000/foo/bar succeeded
# response saved to /home/vagrant/.swat/.cache/32379/prove/N0i8or4eCR
1..1
ok
/home/vagrant/.swat/.cache/32379/prove/00.GET.t ...........
ok 1 - GET 127.0.0.1:3000/ succeeded
# response saved to /home/vagrant/.swat/.cache/32379/prove/eQpLp7zbAw
1..1
ok
Test Summary Report
-------------------
/home/vagrant/.swat/.cache/32379/prove/unknown/00.GET.t (Wstat: 256 Tests: 1 Failed: 1)
Failed test: 1
Non-zero exit status: 1
Files=4, Tests=4, 1 wallclock secs ( 0.03 usr 0.02 sys + 0.28 cusr 0.02 csys = 0.35 CPU)
Result: FAIL
Итак, отлично, мы видим, что SWAT работает, успешно валидируя неизвестные ресурсы, а точнее попросту анализируя http статусы ответов, пришедших с сервера.
Немного слов хочется сказать о том, как SWAT делает запросы. Для генерации http запросов используется утилита curl. Почему же был выбран curl, а не использована, например какая-нибудь библиотека для Perl? ( например, LWP ). IMHO одно из важных преимуществ в случае с curl — простота использования, хорошая документация и поддержка ( недавно я завел некритичный тикет, на который мне ответили в считанные минуты ). За все время пользования этой утилитой я едва ли столкнулся с видами http запросов, которые нельзя реализовать через интерфейс командной строки curl, разве что web сокеты curl не умеет…
Итак, SWAT использует curl для создания http запросов. Это значит, что все параметры необходимые для запроса вы пишите в терминах этой утилиты. Для этого используются так называемые настройки запросов — swat.ini файлы, которые создаются пользователем внутри директорий с ресурсами и позволяют задавать дополнительные параметры для каждого тестового сценария, определяющие его поведение, и в том числе и параметры вызова curl. Приведу примеры:
$ cat bar/baz/swat.ini
# передать именованные параметры через POST/GET запрос
curl_params="-d name=Alexey -d age=39"
$ cat foo/bar/swat.ini
# передать http заголовки
curl_params="-H 'Content-Type: application/json'"
$ cat login/swat.ini
# обеспечить http basic аутентефикацию
curl_params="-ufoo-user:foo-password'"
$ cat slow-route/swat.ini
# задать кол-во повторных запросов в случае неуспешного ответа от сервера
try_num=3
Подробнее со списком параметров, которые можно устанавливать в swat.ini файлах можно ознакомится в документации
Важно заметить, что swat.ini файлы — это обычные bash скрипты, в которых помимо всего прочего можно использовать стандартные bash конструкции, простора для творчества много, для ознакомления с примерами — отсылаю заинтересованных читателей к страницам документации SWAT.
Окей, все хорошо, но как можно было заметить, мы еще не разу не говорили проверке ответов от сервера, за исключением, правда лишь http статуса. Все, что у нас получилось понять, запустив выше указанный набор тестов, что определенный набор ресурсов приложения доступен и что сервер вернул успешный (200 OK) ответ при запросе к этим ресурсам.
Настало время поговорить о SWAT DSL.
SWAT DSL
SWAT DSL — второй ( после описаниея ресурсов и настроек запросов ) основной компонент фреймворка. DSL позволяет проверить ответы, пришедшие от сервера на соответствие некоторым правилам, описанным в виде однострочных утверждений:
RULE1
RULE2
# И так далее ...
Если соответствие утверждению не найдено — генерируется ошибка прохождения теста. Для каждого утверждения ответ проверяется заново. Это формальное описание процесса валидации. На самом деле все проще, утверждения — это просто обычные строчки текста или регулярные выражения, которые вы хотели бы увидеть в ответе сервера.
Приведу примеры:
# успешный ответ от сервера
# проверка излишняя, так как SWAT делает ее автоматически,
# но тем не менее привожу для примера
200 OK
# соответствие одному из трех цветов
regexp: (red|green|blue)
# просто соответствие строке как есть
# обратите внимание , что здесь требуется включение, а не точное соответствие
# что бы было по-другому смотрите следующий пример
HELLO WORLD
# строгое соответствие
regexp: ^HELLO WORLD$
После приведенных примеров хочется добавить следующее:
- SWAT предоставляет DSL не напрямую, а использует готовый модуль для валидации текстовых данных — Outthentic-DSL. Для более глубоко понимания возможностей SWAT DSL — пользуйтесь документацией по данному модулю
- при написании правил или ( синоним для данного тремина ) проверочных утверждний можно использовать комментарии
- при парсинге DSL скрипта строки, содержавшие только пробельные символы или пустые строки игнорируются
- SWAT использует построчный режим проверки, это означает, что ответ от сервера разбивается на строчки и каждая строка сравнивается с очередным правилом. Если хотя бы одна строчка соответствует правилу — тест на данное правило считается пройденным, в противном случае непройденным, о чем SWAT сообщает в выводе от консольного клиента
- если хочется многострочных проверок — можно использовать SWAT блоки
- синтакцис регулярных выражений должен соответствовать регулярным выражениям в Perl, т.к. SWAT DSL написан на Perl
- проверочные правила могут быть статичными, т.е. описанными заранее, как в приведенном здесь примере, но SWAT предоставляет возможность задавать такие правила динамически, через программный API посредством механизма генераторов
- В данной статье невозможно упомянуть о всех возможностях DSL и правилах написания проверочных утверждений — так как они достаточно разнообразны и многочисленны, опять таки же заинтересованного читателя отсылаю к страницам документации
Итак, мы имеем DSL для написания проверочных правил, остается вопрос, где мы будем создавать эти правила? Ответ напрашиватеся сам по себе — конечно же в файлах http методов! ( Вспоминаем ремарку про расширения *.txt в именах файлах методов ).
Перепишем, а точнее обновим наш предыдущий пример, добавив несколько проверок на ответ от сервера на при запросе различных ресурсов:
$ cat get.txt
HELLO USER! THIS IS INDEX PAGE
$ cat foo/bar/get.txt
I AM FOO-BAR
$ cat bar/baz/post.txt
POST TO BAR-BAZ OK
Теперь запустим тесты заново:
$ cd swat-project
$ swat ./ 127.0.0.1:3000
/home/vagrant/.swat/.cache/1422/prove/foo/bar/00.GET.t ...
ok 1 - GET 127.0.0.1:3000/foo/bar succeeded
# response saved to /home/vagrant/.swat/.cache/1422/prove/CmDiEY28iD
ok 2 - output match 'I AM FOO-BAR'
1..2
ok
/home/vagrant/.swat/.cache/1422/prove/bar/baz/00.POST.t ..
ok 1 - POST 127.0.0.1:3000/bar/baz succeeded
# response saved to /home/vagrant/.swat/.cache/1422/prove/rX8oenyA0j
ok 2 - output match 'POST TO BAR-BAZ OK'
1..2
ok
/home/vagrant/.swat/.cache/1422/prove/00.GET.t ...........
ok 1 - GET 127.0.0.1:3000/ succeeded
# response saved to /home/vagrant/.swat/.cache/1422/prove/PxDCnlbOA5
ok 2 - output match 'HELLO USER! THIS IS INDEX PAGE'
1..2
ok
All tests successful.
Files=3, Tests=6, 0 wallclock secs ( 0.03 usr 0.00 sys + 0.24 cusr 0.00 csys = 0.27 CPU)
Result: PASS
Как мы видим, SWAT сделал свое дело и провалидировал ответы от сервера. На этом первое знакомство со SWAT DSL можно завершить.
В конце данной стать я хотел бы упомянуть еще об одной интересной особенности SWAT, позволяющей писать не только простейшие проверки, но и полноценные функциональные тесты ( помните в самом начале я сказал, что SWAT способен на многое?… ). Итак, поговорим о повторно используемых http запросах.
Повторно используемые http запросы
Любой мало мальски сложное web приложение или сервис, будучи разложено на множество отдельных ресурсов или маршрутов, на поверку оказывается сильно «связанным» относительно этих ресурсов. Вот, что я хочу сказать — запрос в какой-то ресурс подразумевает зачасутю подразумевает предварительный запрос в другой ресурс. Т.е. речь уже идет не одиночных и независимых запросах отдельных ресурсов, а о цепочках таких запросов. Примеры очевидны:
- доступ к ресурсам, требующих аутентификации / авторизации
- создание записей в базе данных, требующее предварительное удаление старой записи
- условные запросы — удалить запись из базы, если она там есть
И так далее…
Очевидно, что при подходе «один тест — один ресурс — один запрос», подобные тестовые сценарии неосуществимы. Необходимо каким-то образом перед запросом в один ресурс, сделать запросы в один или, возможно, в несколько других ресурсов. Как же быть? И тут нам на помощь приходят SWAT модули — повторно используемые http запросы.
В общем смысле SWAT модули очень похоже на функции, определив которые единожды, вы можете вызвать их сколь угодно раз, в другом месте, при необходимости передав на вход модуля некоторые исходные параметры и обработав полученный результат, в SWAT это реализуется через механизм upstream / downstream историй.
Итак, что нужно понимать о SWAT модулях:
- это такие же http ресурсы, как все остальные созданные в SWAT проекте, с той лишь разницей, что SWAT никогда не вызовет ( осуществит, связанный с ресурсом http запрос ) модуль напрямую
- другой ресурс может вызвать SWAT модуль посредством SWAT HOOKs API
- вызов SWAT модуля в другом ресурсе означает, что сначала SWAT сделает http запрос ресурса модуля и валидирует ответ ( т.е. прогонит всю цепочку проверок, связанную с данным ресусром, начиная от проверки http статуса, до валидации через правила, определенные для ресурса ), а затем уже сделает запрос основного ресурса, в котором вызывался SWAT модуль.
- На странице документации SWAT ресурс, вызывающий какой-то SWAT модуль называется upstream story — основная история, а вызываемый SWAT модуль — downstream story — второстепенная история. Терминология взята из jenkins-ci.org, хотя в SWAT эта модель инвертирована, т.к. в Jenkins downstream job вызывается после выполнения основной задачи
- SWAT HOOKs API — возможность расширения тестовых сценариев SWAT за счет написание кода на Perl, в данном контексте HOOKS API, позволяет в том числе программно вызывать SWAT модули
Поясним все вышесказанное на простом примере. Пусть у нас есть web сервис предоставляющий доступ к двум ресурсам:
POST login/ — Ресурс для аутентификации пользователя. В случае передачи на сервер валидных логина и пароля приложение возвращает куки сессию. Для простоты будем считать, что сервер принимает логин и пароль через именованные поля POST запроса — user и password.
GET restricted/ — Доступ к защищенному ресурсу, могут иметь только аутентефицированные пользователи.
Напишем SWAT тест для такого приложения. Очевидная тестовая история здес — аутентефицироваться и получить доступ к к защищенному ресурсу GET restricted.
Создадим скелет проекта, описав ресурсы и методы:
$ mkdir swat-project
$ cd swat-project
$ mkdir login
$ mkdir restricted
$ touch login/post.txt
$ touch restricted/get.txt
Теперь нам нужно объявить, что ресурс login/ — SWAT модуль, т.к. мы захотим вызвать его перед вызовом ресурса GET restricted/.
Используем для это файл настроек ресурса — swat.ini файл:
$ cat login/swat.ini
swat_module=1
Установка переменной swat_module в единицу объявляет ресурс в качестве SWAT модуля.
Добавим параметры необходимые для аутентификации. В данном случае, для простоты примера логин и пароль задаются явно, однако SWAT так же позволяте выносить подобноого рода параметры из проекта и задавать их в другом месте исходя из соображений безопасности.
$ cat login/swat.ini
swat_module=1
сurl_params="-d user=my-login -d password=my-password"
Да и наконец последний штрих — сервер в случае успешной аутентификации вернет нам куки, мы должны их где-то сохранить, хранить будем используя механизм cookie-jar все той же утилиты curl, окончательный вариант настроек запроса к ресурсу будте таким:
$ cat login/swat.ini
swat_module=1
# $test_root_dir - предопределенная переменная, определяющая временную директорию
# в которой будут запускаться тесты
# очень удобно складывать сюда различные временные данные
сurl_params="-d user=my-login -d password=my-password --cookie-jar $test_root_dir/cookie.txt"
Окей, ресурс POST login/ готов. Если мы знаем, что кроме успешного статуса ( 200 OK ) должен вернуть сервер ( а мы знаем ;-) ), можно добавить дополнительную проверку в файле метода:
$ cat login/post.txt
hello user!
Теперь вызовем POST login/ перед вызовом GET restricted/, делается это так:
$ cat restricted/hook.pm
run_swat_module( POST => '/login' );
Что мы сделали? Мы создали хук файл для ресурса GET restricted/ и потребовали, что бы в самом начале был сделан запрос POST login/
Что нам осталось? Настроить вызов GET restricted/ что бы он использовал куки, которые будут созданы после успешной аутентефиакции посредством POST login/:
$ cat restricted/swat.ini
сurl_params="--cookie $test_root_dir/cookie.txt"
Ну и аналогично ресурсу POST login/ можно задать дополнительную проверку на ответ от сервера в случае с запросом GET restricted/:
$ cat login/get.txt
restricted content
О чем бы еще хотелось упомянуть в вкратце относительно повторного используемых запросов SWAT ( к сожалению формат статьи не позволяет раскрыть полностью весь материал )? Перечислю тезисно:
- каскадные вызовы SWAT модулей — один модуль может вызывать другой и так далее, все как с обычными функциями
- передач данных ( состояний ) между основным ресурсом и вызванными в нем SWAT модулями. Да возможна, т.к. чисто технически модуль и вызвавший его код выполняются в одном процессе, смотрите документацию
- передача параметров на вход SWAT модуля и доступ к ним внутри кода реализующего модуль — это называется переменные модуля, опять таки же смотрите в документацию
Заключение
Не хочется заканчивать статью банальной фразой — «а теперь идите на страницу проекта, узнайте как установить утилиту и начинайте ее использовать ...», хотя было бы не плохо :-)
Но вот почему бы вы могли начать использовать SWAT в вашей работе ( IMHO ):
- позволю себе вольно перефразировать Лари Уола — SWAT позволяет делать простые вещи простo и сложные осуществимыми. Заметьте как мало кода нам пришлось написать в наших примерах, и в тоже время — SWAT мощное средство с возможностью расширения на Perl. Будь вы сисадмин с базовыми навыками программирования или же опытный разработчик — SWAT может быть одинаково удобным и понятными для вас
- SWAT масксимально прагматичен. Он был придуман исходя из реалий жизни для решения конкретных задач — а именно быстрой разработки автотестов для большого количества приложений, подвергающихся частым обновлениям. Из SWAT убрана вся лишняя функциональность, он максимально заточен под решение проблемы и не предоставляет нечего «в нагрузку»
- SWAT построен на широко используемых и зарекомендовавших себя решениях, известных как системным администраторам так и разработчикам — а именно — curl и bash. Если вы знаете curl — значит вы уже знакомы с синтаксисом SWAT на половину, SWAT просто транслирует настройки в swat.ini файлах напрямую в curl ( ну или почти так ;-) ), если вы знаете основы bash — вам не составит труда описать настройки SWAT ресурсов
- SWAT поддерживается проектом-спутником — Sparrow. Это означает что у вас есть удобная инфраструктура по управлению и разработке SWAT проектов и возможность повторно использовать написанные кем-то SWAT тесты, загружая их центрального репозитария SparrowHub, и в свою очередь загружать в этот же репозитарий свои. Подробнее об этом можно почитать тут и тут
Жду комментариев и вопросов.
Спасибо.
P.S. Исходники web приложения и SWAT тестов, упоминаемые в данной статье, можно скачать здесь.
P.P.S. Всех с наступающим!
Комментарии (8)
alexey_melezhik
24.12.2015 16:11+1Еще раз прошу прощения, не сразу с катом разобрался, сейчас должно быть все нормально…
ivanych
24.12.2015 19:25Кажется, я уже говорил в статье про Sparrow, но повторю и тут: лучше вместо «веб-приложение» использовать термин «веб-сервис». Меня, например, термин «веб-приложение» сбивает с толку, как-будто мы тестируем вот прям приложение, некий бинарник:)
ivanych
24.12.2015 22:32Отлично, прочитал — всё понятно, ссылки на подробности добавлены в нужных местах.
Но, мне кажется, стоило бы включить в статью пример вызова модуля с передачей параметров — уж очень это распространенная и часто возникающая ситуация.alexey_melezhik
24.12.2015 22:40Да, согласен, важная штука, без входных параметров модули не так эффективны. Просто не хотел делать статью слишком перегруженной материалом, написать-то можно было о многом :-)
Crandel
Спойлер — где же ты?)