«В жизни каждого django-разработчика наступает момент, когда он решительно рвет со своим прошлым, лишенным функционального тестирования!»
Об этом и поговорим.
Эта статья – результат поиска оптимального инструмента тестирования готовых страниц небольшого проекта. За критерий оценки оптимальности инструмента условно примем минимальное время выполнения теста при стремлении к равным условиям каждого из наборов приложений.
В статье рассматриваются 3 варианта функционального тестирования django приложения на python 3.4 под django 1.7 с помощью Selenium WebDriver (на Хабре есть замечательные статьи с подробным описанием возможностей Selenium тут и тут). А тут подробная информация по Selenium WebDriver API. Еще документация, на русском и примеры использования.
В тестах использовались технологии и приложения: VirtualBox 4.3.6, Debian 7.7, virtualenv 12.0.7, python 3.4, django 1.7, selenium webdriver 2.47.3, iceweasel, xvfb, PyVirtualDispley 0.1.5, PhantomJS 1.9.8, nginx 1.2.1, uWSGI.
Рассмотрим следующие связки для Debian 7.7 + virtualenv 12.0.7 + django 1.7 +:
- 1 вариант. X.org + Selenium WebDriver + Firefox (Iceweacel для debian) — с графической оболочкой
- 2 вариант. Terminal + Selenium WebDriver + Xvfb, PyVirtualDisplay — запуск через терминал
- 3 вариант. Terminal + Selenium + PhantomJS — запуск через терминал
Инструменты функционального тестирования позволяют провести оценку работоспособности конечного сайта и поведения страниц в браузере, а так-же помогают если базовый тестовый клиент Django оказывается не удобен при тестировании динамически подгружаемых данных (при использовании JavaScript, Ajax). Когда различные технологии объединены в едином продукте и возникает закономерный вопрос, как они поведут себя вместе в том или ином случае.
При помощи этих инструментов мы можем симулировать реальное поведение пользователей.
Приступим!
В виртуальное окружение ставим Selenium:
pip install selenium
В нашем случае будем тестировать процесс аутентификации пользователя, поэтому файл tests.py создадим в приложении authentication (Пункт Б).
Вариант 1. Визуальное симулирование поведения пользователя
А. Предполагаем, что графический интерфейс установлен, тут — X.org.
Ставим Debian Iceweasel (ранее Debian Firefox — модификация браузера Mozilla Firefox в Debian GNU/Linux):
sudo apt-get install iceweasel
! Примечание:
При попытке запустить тест из терминала, без запуска графической среды, мы получим исключение: raise WebDriverException(«The browser appears to have exited „selenium.common.exceptions.WebDriverException: Message: The browser appears to have exited before we could connect. If you specified a log_file in the FirefoxBinary constructor, check it for details.)
Б. Создаем файл tests.py.
Вариант 1
# Подключить встроенный сервер Django для использования клиента Selenium
from django.test import LiveServerTestCase
# Подключить вебдрайвер управления браузером (тут FireFox)
from selenium import webdriver
import time
class SeleniumTests(LiveServerTestCase):
def test_auth(self):
# Подключить webdriver Firefox
br = webdriver.Firefox()
# Перейти на главную страницу, получив адрес сервера 'localhost:8081' и полный URL
br.get('%s%s' % (self.live_server_url, '/'))
# Перейти по ссылке регистрации
br.find_element_by_xpath('//a[@href="/register/"]').click()
# Подождать 3 секунды
time.sleep(3)
# Регистрация пользователя
# Найти поле username и указать значение 'new'
br.find_element_by_id('username').send_keys('new')
# Найти поле email и указать значение 'new@new.ru'
br.find_element_by_id('email').send_keys('new@new.ru')
# Указать пароль в 2-ух полях
br.find_element_by_id('password1').send_keys('12345678')
br.find_element_by_id('password2').send_keys('12345678')
# Перейти по ссылке регистрации
br.find_element_by_id('btn_register').click()
# Активизировать пользователя в тестовой БД
pis = Myuser.objects.get(username='new')
# Поставить признак пользователя - Активен
pis.is_active = True
# Сохранить в БД
pis.save()
# Перейти на домашнюю страницу
br.find_element_by_xpath('//a[@href="/"]').click()
# Подождать 3 секунды
time.sleep(3)
# Войти под зарегистрированным пользователем
br.find_element_by_id('username').send_keys('new')
br.find_element_by_id('password').send_keys('12345678')
br.find_element_by_name('Вход').click()
# Проверить наличие имени авторизованного пользователя в соответствующем теге
assert br.find_element_by_xpath('//a[@data-content="Личный кабинет"]').text == 'new'
# Отключить вебдрайвер, закрыть браузер
br.quit()
! Примечание:
В тексте программы необходимо ставить паузу time.sleep(3) во избежание ошибки: “selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: {»method":«id»,«selector»:«username»}", «Не удается найти элемент», вызванной тем, что не успел подгрузиться весь контекст страницы. При возникновении ошибки можно увеличить время ожидания.
В. Запускаем наш тест из authentication/tests.py:
python manage.py test authentication/
Еще варианты запуска.
Всех тестов проекта:
python manage.py test
Конкретного класса SeleniumTests из tests.py:
python manage.py test authentication.tests.SeleniumTests
Метода test_auth класса SeleniumTests:
python manage.py test authentication.tests.SeleniumTests.test_auth
ВРЕМЯ РАБОТЫ ТЕСТА (Debian 7.7, VirtualBox, 12 Mb VideoRAM, 512 Mb RAM, 1 core): (29.89 + 25.65 + 26.73)/3 = 27.42 сек
Вариант 2. Запуск тестов через терминал без запуска графической оболочки на xvfb + pyvirtualdisplay
А. Для запуска из консоли необходимо установить в виртуальном окружении virtualenv х-сервер: xvfb и виртуальный дисплей: pyvirtualdisplay.
sudo apt-get install xvfb
pip install pyvirtualdisplay
! Примечание:
PyVirtualDisplay ставим от имени локального пользователя, иначе при тестировании из-под пользователя в строке импорта: from pyvirtualdisplay import Display, будет ошибка.
PyVirtualDisplay 0.1.5 официально поддерживает версии python: 2.6, 2.7, 3.2, 3.3, проверено на 3.4 — работает
Б. Создаем файл tests.py. К ранее описанному в Варианте 1 добавляем строки для работы с виртуальным дисплеем.
Вариант 2
# Подключить виртуальный дисплей
from pyvirtualdisplay import Display
# Подключить встроенный сервер Django для использования клиента Selenium
from django.test import LiveServerTestCase
# Подключить вебдрайвер управления браузером (тут FireFox)
from selenium import webdriver
import time
class SeleniumTests(LiveServerTestCase):
def test_auth(self):
# Инициализировать и запустить виртуальный дисплей
display = Display(visible=0, size=(800, 600))
display.start()
# Подключить webdriver Firefox
br = webdriver.Firefox()
# Перейти на главную страницу, получив адрес сервера 'localhost:8081' и полный URL
br.get('%s%s' % (self.live_server_url, '/'))
# Перейти по ссылке регистрации
br.find_element_by_xpath('//a[@href="/register/"]').click()
# Подождать 3 секунды
time.sleep(3)
# Регистрация пользователя
# Найти поле username и указать значение 'new'
br.find_element_by_id('username').send_keys('new')
# Найти поле email и указать значение 'new@new.ru'
br.find_element_by_id('email').send_keys('new@new.ru')
# Указать пароль в 2-ух полях
br.find_element_by_id('password1').send_keys('12345678')
br.find_element_by_id('password2').send_keys('12345678')
# Перейти по ссылке регистрации
br.find_element_by_id('btn_register').click()
# Активизировать пользователя в тестовой БД
pis = Myuser.objects.get(username='new')
# Поставить признак пользователя - Активен
pis.is_active = True
# Сохранить в БД
pis.save()
# Перейти на домашнюю страницу
br.find_element_by_xpath('//a[@href="/"]').click()
# Подождать 3 секунды
time.sleep(3)
# Войти под зарегистрированным пользователем
br.find_element_by_id('username').send_keys('new')
br.find_element_by_id('password').send_keys('12345678')
br.find_element_by_name('Вход').click()
# Проверить наличие имени авторизованного пользователя в соответствующем теге
assert br.find_element_by_xpath('//a[@data-content="Личный кабинет"]').text == 'new'
# Остановить виртуальный дисплей
display.stop()
# Отключить вебдрайвер, закрыть браузер
br.quit()
ВРЕМЯ РАБОТЫ ТЕСТА (Putty SSH, Screen, virtualenv, Debian 7.7, VirtualBox, 12 Mb VideoRAM, 512 Mb RAM, 1 core): (28.5 + 25.82 + 24.98)/3 = 26.43 сек
Вариант 3. Запуск тестов через терминал без запуска графической оболочки на PhantomJS
А. Для запуска из консоли необходимо установить phantomjs:
cd /usr/local/share
sudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2
sudo tar xjf phantomjs-1.9.8-linux-x86_64.tar.bz2
sudo ln -s /usr/local/share/phantomjs-1.9.8-linux-x86_64/bin/phantomjs /usr/local/share/phantomjs
sudo ln -s /usr/local/share/phantomjs-1.9.8-linux-x86_64/bin/phantomjs /usr/local/bin/phantomjs
sudo ln -s /usr/local/share/phantomjs-1.9.8-linux-x86_64/bin/phantomjs /usr/bin/phantomjs
И запускаем:
phantomjs -v
! Примечание:
Если полученный пакет не годится данной системе, получим ошибку: «Не могу запустить бинарный файл» (Часто из-за попытки установить на 32-ух разрядную систему (x86) 64-разрядное приложение (x64)).
Б. Создаем файл tests.py. К ранее описанному в Варианте 1 добавляем строки для работы с PhantomJS.
Вариант 3
# Подключить PhantomJS
from selenium.webdriver import PhantomJS
# Подключить встроенный сервер Django для использования клиента Selenium
from django.test import LiveServerTestCase
import time
# НЕ НУЖЕН
# Подключить вебдрайвер управления браузером (тут FireFox)
#from selenium import webdriver
class SeleniumTests(LiveServerTestCase):
def test_auth(self):
# НЕ НУЖЕН
# Подключить webdriver Firefox
#br = webdriver.Firefox()
# Инициализировать драйвер PhantomJS
br = PhantomJS()
# установить разрешение страницы
br.set_window_size(800, 600)
# Перейти на главную страницу, получив адрес сервера 'localhost:8081' и полный URL
br.get('%s%s' % (self.live_server_url, '/'))
# Перейти по ссылке регистрации
br.find_element_by_xpath('//a[@href="/register/"]').click()
# Подождать 3 секунды
time.sleep(3)
# Регистрация пользователя
# Найти поле username и указать значение 'new'
br.find_element_by_id('username').send_keys('new')
# Найти поле email и указать значение 'new@new.ru'
br.find_element_by_id('email').send_keys('new@new.ru')
# Указать пароль в 2-ух полях
br.find_element_by_id('password1').send_keys('12345678')
br.find_element_by_id('password2').send_keys('12345678')
# Перейти по ссылке регистрации
br.find_element_by_id('btn_register').click()
# Активизировать пользователя в тестовой БД
pis = Myuser.objects.get(username='new')
# Поставить признак пользователя - Активен
pis.is_active = True
# Сохранить в БД
pis.save()
# Перейти на домашнюю страницу
br.find_element_by_xpath('//a[@href="/"]').click()
# Подождать 3 секунды
time.sleep(3)
# Добавлено:
# Сделать скриншот всей страницы
br.save_screenshot('screenshot_firstpage.png')
# Войти под зарегистрированным пользователем
br.find_element_by_id('username').send_keys('new')
br.find_element_by_id('password').send_keys('12345678')
br.find_element_by_name('Вход').click()
# Проверить наличие имени авторизованного пользователя в соответствующем теге
assert br.find_element_by_xpath('//a[@data-content="Личный кабинет"]').text == 'new'
# Отключить вебдрайвер, закрыть браузер
br.quit()
У PhantomJS при создании скриншота есть проблема с фоном, при сохранении он прозрачный (черный фон). Для исправления этого недоразумения перед br.save_screenshot('screenshot_firstpage.png') можно использовать
такой код:
br.execute_script("""(function() {
var style = document.createElement('style'), text = document.createTextNode('body { background: #fff }');
style.setAttribute('type', 'text/css');
style.appendChild(text);
document.head.insertBefore(style, document.head.firstChild);
})();""")
var style = document.createElement('style'), text = document.createTextNode('body { background: #fff }');
style.setAttribute('type', 'text/css');
style.appendChild(text);
document.head.insertBefore(style, document.head.firstChild);
})();""")
ВРЕМЯ РАБОТЫ ТЕСТА без создания скриншота страницы (Putty SSH, Screen, virtualenv, Debian 7.7, VirtualBox, 12 Mb VideoRAM, 512 Mb RAM, 1 core): (14.64 + 14.89 + 13.05)/3 = 14.19 сек.
Добавлено:
ВРЕМЯ РАБОТЫ ТЕСТА со скриншотом страницы (Putty SSH, Screen, virtualenv, Debian 7.7, VirtualBox, 12 Mb VideoRAM, 512 Mb RAM, 1 core): (17.63 + 17.74 + 17.94)/3 = 17.77 сек.
Резюме:
Использование Варианта 3 без скриншота страницы — [14.19 сек.] предоставляет почти в 2 раза меньше времени на перекуры, чем Вариант 2 — [26.43 сек.] и Вариант 1 — [27.42 сек.] и годится для использования на боевом сервере без графической оболочки. Но Вариант 1 — визуально информативнее и удобен для предварительных тестов при разработке, перед выкаткой кода на боевой сервер.
ПС. Тут нужно дополнительно учесть, что в коде теста в общей сложности использованы 6 сек. ожидания.
Добавлено:
Уважаемые коллеги, а какими связками при функциональном тестировании пользуетесь Вы? Какие у них преимущества и недостатки?
Спасибо за внимание!