image

«В жизни каждого 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);
})();""")


ВРЕМЯ РАБОТЫ ТЕСТА без создания скриншота страницы (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 сек.

image



Резюме:


Использование Варианта 3 без скриншота страницы — [14.19 сек.] предоставляет почти в 2 раза меньше времени на перекуры, чем Вариант 2 — [26.43 сек.] и Вариант 1 — [27.42 сек.] и годится для использования на боевом сервере без графической оболочки. Но Вариант 1 — визуально информативнее и удобен для предварительных тестов при разработке, перед выкаткой кода на боевой сервер.

ПС. Тут нужно дополнительно учесть, что в коде теста в общей сложности использованы 6 сек. ожидания.

Добавлено:
Уважаемые коллеги, а какими связками при функциональном тестировании пользуетесь Вы? Какие у них преимущества и недостатки?

Спасибо за внимание!

Комментарии (0)