
«В жизни каждого 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, PyVirtualDisplay 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 X-сервер: 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 сек. ожидания.
Добавлено:
Уважаемые коллеги, а какими связками при функциональном тестировании пользуетесь Вы? Какие у них преимущества и недостатки?
Спасибо за внимание!
