Введение
На данный момент самым популярным решением для клиентского тестирования является selenium. Стоит заметить, что вполне заслужено — те возможности, которые предоставляет selenium в связке с webdriver'ом, действительно охватывают почти весь спектр пользовательского взаимодействия с веб-приложениями.
Для небольших проектов замечательно подходит вариант с плагинами для браузеров, функционал которых можно расширить добавлением сторонних компонентов (например, UI-element). Но когда проект становится достаточно большим, а многие его части многократно изменяются и даже полностью перепиливаются, после каждого изменения в структуре страницы или способе подачи данных приходится заменять сразу целые блоки тестов, заглядывая чуть ли не в каждый тест. После такого selenium плагины уже перестают казаться столь удобными. И тут на помощь приходят библиотеки selenium, реализованные для многих языков ассоциируемых с веб-разработкой (документация на официальном сайте)
Предлагаю вам посмотреть на возможности клиентского тестирования (в частности django проектов), которые дает python-selenium в связке с библиотекой lettuce.
Lettuce + Selenium
Давайте посмотрим на те возможности, которые предоставляют эти компоненты по отдельности:
Python-Selenium
- как было сказано выше, selenium имеет богатый арсенал взаимодествия с веб-приложениями через браузер
- в отличие от плагинов есть возможность использовать обширный функционал python'а
- интеграция с самим проектом
Lettuce
- разделение тестов на слабосвязанные части
- контроль над каждой стадией тестирования
- красивый output в терминале :)
И сразу в бой
На примере простых тестов регистрации и авторизации я постараюсь продемонстрировать основные аспекты работы с lettuce + selenium.
Постановка задачи
Нужно создать 2 теста, которые будут выполнять следущие действия:
Регистрация:
- Зайти на страницу регистрации
- Заполнить поля в форме регистрации
- Нажать на кнопку регистрации
- Увидеть сообщение о успешной регистрации
Авторизация:
- Зайти на страницу авторизации
- Заполнить поля в форме авторизации
- Нажать на кнопку авторизации
- Увидеть сообщение о успешной авторизации
Уже из постановки задачи видно, что эти 2 теста выполняют схожие действия, но в разном контексте. Приступим к выполнению задачи.
Выполнение задачи
Добавив lettuce в наш django проект, как это описано в официальной документации, и установив все необходимые зависимости (в нашем случае это системные пакеты firefox и питонвские модули lettuce и selenium) можно приступать к написанию тестов.
P.S.: для запуска в режиме headless вам пригодятся xvfb и pyvirtualdisplay
Для работы с lettuce необходимо создать следущие файлы:
- terrain.py в корне проекта содержит инструкции, выполняемые на указанной стадии тестирования
- *.feature файлы в папке myapp/features содержит пошаговое описание каждого теста
- *.py файлы в папке myapp/features содержат описания шагов, используемых в *.feature файлах
От себя могу посоветовать включить в проект файл, содержащий структуру страниц вашего приложения (по аналогии с PageObject, используемым в selenium). Назовем этот файл mapping.py и положим в корень проекта. Данное отделение структуры страниц от тестов снижает потребность в переписывании самих тестов при изменении верстки: достаточно поправить соответствующую страницу в mapping.py. Так же это существенно увеличивает читаемость тестов (дальше это будет наглядно показано).
mapping.py
host_url = 'http://example.com'
site_mapping = {
"registration": {
"url": host_url + "/registration/",
"username": "//input[@id='username']",
"email": "//input[@id='email']",
"password": "//input[@name='password1']",
"verify password": "//input[@name='password2']",
"signup button": "//button[@name='signup']",
"status message": "//div[@id='status']",
},
"authorization": {
"url": host_url + "/login/",
"username": "//input[@id='username']",
"password": "//input[@name='password']",
"login button": "//button[@name='login']",
"status message": "//div[@id='status']",
},
}
P.S.: Я использую xpath, т.к. считаю это самым оптимальным способом поиска элементов
terrain.py
from lettuce import before, after, world
from selenium import webdriver
from mapping import site_mapping
@before.harvest
def setup(server):
# world - переменная, используемая lettuce между всеми стадиями тестов, т.е. хранящая в себе информацию между тестами
world.browser = webdriver.Firefox() # открываем браузер
world.mapping = site_mapping # сохраняем структуру в world
@after.all
def teardown(total):
world.browser.close() # закрываем браузер
myapp/features/auth.feature
Feature: Authorization
Scenario: Registration
Open "registration" page
Fill "username" with "myusername"
Fill "email" with "user@example.com"
Fill "password" with "1234"
Fill "verify password" with "1234"
Click "signup button"
See "Welcome aboard" in "status message"
Scenario: Authorization
Open "authorization" page
Fill "username" with "myusername"
Fill "password" with "1234"
Click "login button"
See "Hello! Again..." in "status message"
Как вы видите, отсутствие информации о расположении элементов сыграло роль на читаемости сценариев. Спасибо mapping.py за это
myapp/features/steps.py
from lettuce import step, world
@step(r'Open "(.*)" page')
def open_page(step, page):
world.current_page = world.mapping[page] # взять url искомой страницы
world.browser.get(world.current_page['url']) # открыть этот url в браузере
@step(r'Fill "(.*)" with "(.*)"')
def fill_element_with_text(step, element, value):
elem = world.browser.find_element_by_xpath(world.current_page[element])
elem.send_keys(value)
@step(r'Click "(.*)"')
def i_click_xpath(step, element):
elem = world.browser.find_element_by_xpath(world.current_page[element])
elem.click()
@step(r'See "(.*)" in "(.*)"')
def i_see_text_in_element(step, text, element):
elem = world.browser.find_element_by_xpath(world.current_page[element])
assert elem.text == text
Вот и все. Осталось только запустить тесты и смотреть, как они успешно (или не очень) проходят.
Кажется, что слишком много телодвижений для написания каких-то двух тестов — так и есть. Но если вы хотите покрыть проект большим количеством тестов, то по мере написания сценариев и шагов к ним, вам все меньше будет необходимо писать новые шаги, т.к. почти все возможное взаимодействие пользователя с вашим сайтом уже будет описано в существующих шагах — останется только писать новые сценарии, что не сложнее, чем было написать пункт «постановка задачи» чуть выше.
Итог
Что имеем в остатке:
- Инструмент, которым могут пользоваться как разработчики, так и тестировщики.
- Высокая устойчивость к изменениям в верстке и структуре сайта в целом благодоря mapping.py
- Можно запускать тесты практически в чем угодно. Firefox, chrome, phantomjs (возможно есть и другие варианты, но это то, что пробовал я).
- Запускать тесты где угодно: на своем компьютере, на виартуальной машине, на удаленном сервере
- Для каждого прогона тестов можно создавать тестовую базу данных, включать отдельный инстанс вашего приложения, подгружать тестовые данные, а по окончании все это благополучно удалять (как это делает тестовый фреймворк в самом django).
- Можно прицепить сохранение логов. А если к этому приурочить асинхронные таски, то можно каждые N часов получать информацию о состоянии всего проекта.
- При большой базе шагов (steps.py) достаточно будет описывать только сценарии, которые довольно просто писать «переводя» в них ТЗ.
- Развернув такие тесты на одном проекте не составит большого труда перенести их на другой проект. При переносе на новый проект больше всего времени займет написание сценариев, что не кажется таким уж сложным (по сравнению, например, с созданием очередной сотни тестов в плагинах selenium)
Полезные ссылки по теме:
- lettuce
- официальная документация selenium
- хорошая документация по python-selenium
- немного устаревшая, но годная статья по lettuce + splinter
P.S.: Надеюсь, что кто-нибудь заинтересуется этой темой и будет резонно написать про подводные камни, с которыми я столкнулся, используя lettuce + selenium. Буду рад ответить на ваши вопросы.
P.P.S: Заранее извиняюсь за стиль изложения, пунктуацию и вообще. Первая статья и все такое…