Тестирование веб-проектов. jsFUnit
В профессиональном программировании огромную роль играют автоматезированные тесты. Они заменяют и команду профессиональных тестеров и подсказывают о возникших неполадках в других частях
обширного проекта во время разработки. Работу веб-приложения необходимо тестировать на разных браузерных движках: Gecko, Presto, KHTML, WebKit и Trident.
Разновидностью автоматизированных тестов являются функциональные тесты. Их главная особенность — эмуляция работы пользователя с приложением
через интерфейс. В веб-программировании для написания функциональных тестов используют Selenium. Огромный недостаток Seleniuma — это его невысокая скорость выполнения таких тестов.
Более быстрым фреймворком оказался jsUnit. Правда, jsUnit предназначен для написания unit-тестов, а не функциональных. Обладает он и рядом других мелких недостатков. Как то —
обращение к тестам по их пути, а не выбор теста из списка. В случае хоть одной ошибки весь ползунок закрашивается в красный цвет, а не разделялся на зелёные/красные участки, соответствующие успешно/не успешно выпоненным тестам.
Всё это привело к созданию jsFUnit.
jsFUnit состоит из интерфейса для запуска тестов и двух библиотек: библиотеки проверки условий (различные функции assert) и библиотеки функций эмулирующих работу пользователя с веб-интерфейсом. И скрипта на perl
(update.pl), регистрирующего новые тесты.
jsFUnit я расположил на сурцефорже, откуда и предлагаю загрузить его всем желающим: http://sourceforge.net/projects/jsfunit/files/
После того как скачаете jsFUnit-2.1.zip, распакуйте его в public_html вашего веб-проекта. У вас появится каталог public_html/tests. Надеюсь, что все читатели осведомлены, что такое public_html. Но для тех, кто случайно начал читать статью, предлагаю взять виндовую машину, установить на неё xampp из http://www.apachefriends.org/en/xampp.html в c:\xampp и распаковать jsFUnit-2.1.zip в c:\xampp\htdocs.
В браузере откройте http://localhost/tests (или же какой другой адрес имеет ваш хост). Вы увидите интерфейс jsFUnit. Он довольно простой и вы легко в нём разберётесь.
Для создания теста:
1. Добавте тест в каталог tests/tests-js
2. Тест должен иметь расширение .js
3. Выполните update.pl (он поставляется с jsFUnit и находится в каталоге tests. Это скрипт на perl. Для начинающих поясню:
Пуск -> Выполнить…
cmd
c:\xampp\perl\bin\perl.exe c:\xampp\htdocs\tests\update.pl
)
4. Обновите http://localhost/tests (F5)
Как выглядят тесты?
jsFUnit
похож на jsUnit, jUnit, CUNIT, PHPUnit, NUnit, PyUnit, fUnit, DUnit,
FPCUnit, Test::Class и Test::Unit, CPPUnit, FlexUnit, COSUnit и т.п.
Собственно файл с тестом имеет одну или несколько функций с префиксом test в названии. Которые последовательно запускаются.
function testNagan() {
…
}
function testBlaster() {
…
}
В функциях описываются манипуляции с веб-приложением и проверки на правильность реакции веб-приложения.
function testNagan() {
var nagan = new Nagan()
assertEquals(«Название экземпляра Nagan не типа 'строка'. Соболезнуем», typeof(nagan.name), «string»)
}
Если требуется в начале каждого теста сделать какие-либо инициализирующие действия можно использовать setUp.
А в конце — tearDown.
function setUp() {
protiv_loma_net_prijoma = new Lom()
}
function Nagan() {
assert(«Где вы так метать лом научились?», protiv_loma_net_prijoma.rasstojanie_v_metrax(20))
}
function Sablja() {
assert(«Да вы совсем слабенький, батенька!», protiv_loma_net_prijoma.rasstojanie_v_metrax(1.5))
}
function tearDown() {
protiv_loma_net_prijoma.polozhit()
}
assert([комментарий], значение) — Если значение 0, "", undefined, false или null, то assert прекращает выполнение теста и выводит ошибку в Лог с комментарием, если комментарий указан. Условие должно быть выражением возвращающим значение типа boolean. Пример: assert(«Страшная ошибка!!!», a==10 || x<2)
assertTrue([комментарий], условие) — синоним assert
assertFalse([комментарий], условие) — эквивалент assert, но ошибкой считается, когда условие истинно
assertEquals([комментарий], ожидаемое_значение, полученное_значение) — ошибка, если полученное значение не совпадает с ожидавшимся
assertNotEquals([комментарий], ожидаемое_значение, полученное_значение) — ошибка, если полученное значение совпадает с ожидавшимся
assertIdentity([комментарий], ожидаемое_значение, полученное_значение) — ошибка, если полученное значение не тождественно с ожидавшимся
assertNotIdentity([комментарий], ожидаемое_значение, полученное_значение) — ошибка, если полученное значение тождественно с ожидавшимся
assertNull([комментарий], значение) — ошибка, если значение не null
assertNotNull([комментарий], значение) — ошибка, если значение — null
assertUndefined([комментарий], значение) — ошибка, если значение не undefined
assertNotUndefined([комментарий], значение) — ошибка, если значение — undefined: assertNotUndefined(undefined)
assertNaN([комментарий], значение) — ошибка, если значение не NaN
assertNotNaN([комментарий], значение) — ошибка, если значение — NaN: assertNotNaN(Number.NaN)
fail(комментарий) — ошибка
warn(сообщение, [значение]) — Пишет в лог красным цветом сообщение. Если указано значение, то выводит его как «сообщение: значение»
inform(сообщение, [значение]) — Пишет в лог зелёным цветом
info(сообщение, [значение]) — Пишет в лог зелёным цветом
debug(сообщение, [значение]) — Пишет в лог синим цветом
Данные функции подобны некоторым функциям Seleniumа
Функции для манипулирования окнами и элементами
Функции, заканчивающиеся на Def, в случае неудачного завершения возвращают false, а не вызывают исключение (см. assert).
function testOpen() {
win = getWindow(«MAIN») // Первое, что делает пользователь, это открывает страницу веб-приложения.
// getWindow ждёт пока страница полностью не загрузится
// Страница должна быть в том же домене в котором находится jsFUnit
iframe = getFrameWindow(win, «frame_for_tables») // Иногда необходимо бывает и с фреймами поработать
}
function testForm() {
select(win.document.getElementById(«City»), «St. Peterburg») // выбираем город из выпадающего списка
clickToButton(win, «Применить») // нажимаем на кнопку [Применить]
var pwin = openButton(win, «Пересчитать») // нажимаем на кнопку [Пересчитать] и ждём пока загрузится окно с именем Pereschitatq
clickToLink(pwin, «Меню 1», 2) // Нажимаем на второй элемент на странице pwin с innerHTML = «Меню 1»
}
А теперь подробнее:
getWindowDef(name, [time])
getWindow(name, [time])
Возвращает окно по его имени
Окно:
<html>
<head>
<title>@@Админ или как** </title>
<script>
window.name = "__ADMIN_ILI_KAK__"
</script>
</head>
<body>
</body>
</html>
Тест:
win = getWindow("@@Админ или как** ")
waitCondition(cond, time) — ожидать выполнения cond
если cond не станет true за time милисекунд — выход
cond работает только с глобальными переменными, если задан строкой. Для локальных переменных используйте функцию:
var a = 10, b = 10
waitCondition(function(){ return a == b }, 500)
sleep(time)
спать time милисекунд. Аналогичен:
waitCondition(«false», 500)
waitLoadWindowDef(win, time)
waitLoadWindow(win, time)
ожидает загрузки окна win time милисекунд. Если окно загрузится быстрее, чем за time, то ожидание завершиться тот час же
Внимание! Фунцкция getWindow уже использует waitLoadWindow. waitLoadWindow нужно использовать после window.open:
win = window.open(«localhost/cgi-bin/mypage.pl», «name_window»)
waitLoadWindow(win, 5000)
getFrameWindowDef(win, frame, [time])
getFrameWindow(win, frame, [time])
Ждёт загрузки страницы в указанный фрейм и возвращает объект его окна
Страница в win:
<html>
<body>
<iframe name=«frame_tab» src=«page_for_this_frame»></iframe>
</body>
</html>
Тест:
var win_in_frame = getFrameWindow(win, «frame_tab»)
selectDef(sel, text)
select(sel, text)
Выбирает опцию тега <select> по её тексту
sel – элемент. Получить его можно по имени: select(document.all.reason, «Вот так»)
<select name=«reason»>
<option value=1>А вот и нет
<option value=2>Вот так
</select>
Того же можно добиться и так: document.all.reason.value = 2
linkDef(win, text_in_link, [tag], [event], [number])
link(win, text_in_link, [tag], [event], [number])
возвращет ссылку на элемент с текстом text_in_link: <div>text_in_link</div> или <a>text_in_link</a> или в других элементах
Окно:
<html>
<head>
<script>
window.name=«Admin»
</script>
</head>
<body>
<div id=Div1>Пол<b>ь</b>зователи</div>
<div id=Div2>Пол<b>ь</b>зователи</div>
</body>
</html>
Тест:
win = getWindow(«Админ») // Получаем окно
div = link(win, «Пользователи», 2) // получаем второй элемент с текстом «Пользователи» в нём (Div2)
div.style.color = «red» // делаем его красного цвета
clickToLinkDef(win, text_in_link, [number])
clickToLink(win, text_in_link, [number])
нажимает на элемент указанный по контенту можно указать на какой по счёту элемент нажимать (number)
Окно:
<a href="/users">Пользователи</a>
Тест:
clickToLink(win, «Пользователи»)
clickToButtonDef(win, text_in_value, [number])
clickToButton(win, text_in_value, [number])
нажимает на кнопку можно указать на какой по счёту элемент нажимать (number)
Окно:
<input type=«button» value=«Пользователи»>
Тест:
clickToButton(win, «Пользователи»)
openLink(win, text_in_link, [sleepTime], [time], [number])
нажимает на элемент и возвращает объект одноимённого окна
Окно:
<script src=«tests/WorkWithHtml.js»>
<script>
window.name = translate(«Пользователи»)
// window.name = «Polqzovateli»
</script>
Тест:
win = openLink(win, «Пользователи»)
openButton(win, text_in_value, [sleepTime], [time], [number])
нажимает на кнопку и возвращает объект одноимённого окна
1. Недостатки Selenium-а:
а) требуется устанавливать сервер
б) требователен к ресурсам
в) много времени тратится на запуск сервера + запуск браузера
г) сервер Selenium-а перезапускает браузер для каждого теста
2. Недостатки jsUnit:
а) для каждого теста создаётся html-обёртка
б) для запуска нескольких тестов требуется написать новый тест с функцией suite(), в которой перечислить вызываемые
в) путь к тесту вводится в TestRunner (интерфейсе jsUnit) каждый раз вручную
г) отладочная информация выводятся в отдельное окно, а не в общий лог ошибок
3. Достоинства jsFUnit:
а) не требует сервера
б) не требователен к ресурсам
в) тест или группа тестов выбираются из выпадающего списка
г) группа тестов, которые нужно запустить просто кладутся в отдельный каталог
д) отладочная информация выводится в общий лог
е) можно визуально оценить количество выполненных тестов через закраску ползунка
Таким образом jsFUnit быстр, удобен в использовании и тем самым экономит ваше время!
В профессиональном программировании огромную роль играют автоматезированные тесты. Они заменяют и команду профессиональных тестеров и подсказывают о возникших неполадках в других частях
обширного проекта во время разработки. Работу веб-приложения необходимо тестировать на разных браузерных движках: Gecko, Presto, KHTML, WebKit и Trident.
Разновидностью автоматизированных тестов являются функциональные тесты. Их главная особенность — эмуляция работы пользователя с приложением
через интерфейс. В веб-программировании для написания функциональных тестов используют Selenium. Огромный недостаток Seleniuma — это его невысокая скорость выполнения таких тестов.
Более быстрым фреймворком оказался jsUnit. Правда, jsUnit предназначен для написания unit-тестов, а не функциональных. Обладает он и рядом других мелких недостатков. Как то —
обращение к тестам по их пути, а не выбор теста из списка. В случае хоть одной ошибки весь ползунок закрашивается в красный цвет, а не разделялся на зелёные/красные участки, соответствующие успешно/не успешно выпоненным тестам.
Всё это привело к созданию jsFUnit.
jsFUnit состоит из интерфейса для запуска тестов и двух библиотек: библиотеки проверки условий (различные функции assert) и библиотеки функций эмулирующих работу пользователя с веб-интерфейсом. И скрипта на perl
(update.pl), регистрирующего новые тесты.
jsFUnit я расположил на сурцефорже, откуда и предлагаю загрузить его всем желающим: http://sourceforge.net/projects/jsfunit/files/
После того как скачаете jsFUnit-2.1.zip, распакуйте его в public_html вашего веб-проекта. У вас появится каталог public_html/tests. Надеюсь, что все читатели осведомлены, что такое public_html. Но для тех, кто случайно начал читать статью, предлагаю взять виндовую машину, установить на неё xampp из http://www.apachefriends.org/en/xampp.html в c:\xampp и распаковать jsFUnit-2.1.zip в c:\xampp\htdocs.
В браузере откройте http://localhost/tests (или же какой другой адрес имеет ваш хост). Вы увидите интерфейс jsFUnit. Он довольно простой и вы легко в нём разберётесь.
Для создания теста:
1. Добавте тест в каталог tests/tests-js
2. Тест должен иметь расширение .js
3. Выполните update.pl (он поставляется с jsFUnit и находится в каталоге tests. Это скрипт на perl. Для начинающих поясню:
Пуск -> Выполнить…
cmd
c:\xampp\perl\bin\perl.exe c:\xampp\htdocs\tests\update.pl
)
4. Обновите http://localhost/tests (F5)
Как выглядят тесты?
jsFUnit
похож на jsUnit, jUnit, CUNIT, PHPUnit, NUnit, PyUnit, fUnit, DUnit,
FPCUnit, Test::Class и Test::Unit, CPPUnit, FlexUnit, COSUnit и т.п.
Собственно файл с тестом имеет одну или несколько функций с префиксом test в названии. Которые последовательно запускаются.
function testNagan() {
…
}
function testBlaster() {
…
}
В функциях описываются манипуляции с веб-приложением и проверки на правильность реакции веб-приложения.
function testNagan() {
var nagan = new Nagan()
assertEquals(«Название экземпляра Nagan не типа 'строка'. Соболезнуем», typeof(nagan.name), «string»)
}
Если требуется в начале каждого теста сделать какие-либо инициализирующие действия можно использовать setUp.
А в конце — tearDown.
function setUp() {
protiv_loma_net_prijoma = new Lom()
}
function Nagan() {
assert(«Где вы так метать лом научились?», protiv_loma_net_prijoma.rasstojanie_v_metrax(20))
}
function Sablja() {
assert(«Да вы совсем слабенький, батенька!», protiv_loma_net_prijoma.rasstojanie_v_metrax(1.5))
}
function tearDown() {
protiv_loma_net_prijoma.polozhit()
}
А вот функции jsFUnit для проверок
assert([комментарий], значение) — Если значение 0, "", undefined, false или null, то assert прекращает выполнение теста и выводит ошибку в Лог с комментарием, если комментарий указан. Условие должно быть выражением возвращающим значение типа boolean. Пример: assert(«Страшная ошибка!!!», a==10 || x<2)
assertTrue([комментарий], условие) — синоним assert
assertFalse([комментарий], условие) — эквивалент assert, но ошибкой считается, когда условие истинно
assertEquals([комментарий], ожидаемое_значение, полученное_значение) — ошибка, если полученное значение не совпадает с ожидавшимся
assertNotEquals([комментарий], ожидаемое_значение, полученное_значение) — ошибка, если полученное значение совпадает с ожидавшимся
assertIdentity([комментарий], ожидаемое_значение, полученное_значение) — ошибка, если полученное значение не тождественно с ожидавшимся
assertNotIdentity([комментарий], ожидаемое_значение, полученное_значение) — ошибка, если полученное значение тождественно с ожидавшимся
assertNull([комментарий], значение) — ошибка, если значение не null
assertNotNull([комментарий], значение) — ошибка, если значение — null
assertUndefined([комментарий], значение) — ошибка, если значение не undefined
assertNotUndefined([комментарий], значение) — ошибка, если значение — undefined: assertNotUndefined(undefined)
assertNaN([комментарий], значение) — ошибка, если значение не NaN
assertNotNaN([комментарий], значение) — ошибка, если значение — NaN: assertNotNaN(Number.NaN)
fail(комментарий) — ошибка
warn(сообщение, [значение]) — Пишет в лог красным цветом сообщение. Если указано значение, то выводит его как «сообщение: значение»
inform(сообщение, [значение]) — Пишет в лог зелёным цветом
info(сообщение, [значение]) — Пишет в лог зелёным цветом
debug(сообщение, [значение]) — Пишет в лог синим цветом
Функции манипулирования интерфейсом
Данные функции подобны некоторым функциям Seleniumа
Функции для манипулирования окнами и элементами
Функции, заканчивающиеся на Def, в случае неудачного завершения возвращают false, а не вызывают исключение (см. assert).
function testOpen() {
win = getWindow(«MAIN») // Первое, что делает пользователь, это открывает страницу веб-приложения.
// getWindow ждёт пока страница полностью не загрузится
// Страница должна быть в том же домене в котором находится jsFUnit
iframe = getFrameWindow(win, «frame_for_tables») // Иногда необходимо бывает и с фреймами поработать
}
function testForm() {
select(win.document.getElementById(«City»), «St. Peterburg») // выбираем город из выпадающего списка
clickToButton(win, «Применить») // нажимаем на кнопку [Применить]
var pwin = openButton(win, «Пересчитать») // нажимаем на кнопку [Пересчитать] и ждём пока загрузится окно с именем Pereschitatq
clickToLink(pwin, «Меню 1», 2) // Нажимаем на второй элемент на странице pwin с innerHTML = «Меню 1»
}
А теперь подробнее:
getWindowDef(name, [time])
getWindow(name, [time])
Возвращает окно по его имени
- name — имя окна. Параметр подвергается транслитерации. Например: «Алка*@дмин „ -> “ALKA__DMIN»
- time — сколько ждать полной загрузки страницы в окне.
Необязательный параметр. 5000 по умолчанию. Если страница загрузится быстрее чем за time, то ждать она не будет - window.name нужно присвоить транслитерированный window.document.title. Благодаря чему в name можно указывать для таких окон их заголовок. Это делается функцией set_translit_name_window()
- window.document.title — это заголовок окна браузера
- window.name — имя окна. Должно быть идентификатором, то есть: /[a-z]\w*/i или пусто
Пример:
Окно:
<html>
<head>
<title>@@Админ или как** </title>
<script>
window.name = "__ADMIN_ILI_KAK__"
</script>
</head>
<body>
</body>
</html>
Тест:
win = getWindow("@@Админ или как** ")
waitCondition(cond, time) — ожидать выполнения cond
если cond не станет true за time милисекунд — выход
cond работает только с глобальными переменными, если задан строкой. Для локальных переменных используйте функцию:
var a = 10, b = 10
waitCondition(function(){ return a == b }, 500)
sleep(time)
спать time милисекунд. Аналогичен:
waitCondition(«false», 500)
waitLoadWindowDef(win, time)
waitLoadWindow(win, time)
ожидает загрузки окна win time милисекунд. Если окно загрузится быстрее, чем за time, то ожидание завершиться тот час же
- win – объект окна. Используйте window.frames[«имя фрейма»] или window.open(), чтобы его получить
- time – время в милисекундах
Внимание! Фунцкция getWindow уже использует waitLoadWindow. waitLoadWindow нужно использовать после window.open:
win = window.open(«localhost/cgi-bin/mypage.pl», «name_window»)
waitLoadWindow(win, 5000)
getFrameWindowDef(win, frame, [time])
getFrameWindow(win, frame, [time])
Ждёт загрузки страницы в указанный фрейм и возвращает объект его окна
- win — окно в котором находится фрейм
- frame — имя фрейма, заданное атрибутом name или id
- time — необязательный параметр. Задаёт время ожидания загрузки страницы в фрейм. По умолчанию — 5000 милисекунд
Пример:
Страница в win:
<html>
<body>
<iframe name=«frame_tab» src=«page_for_this_frame»></iframe>
</body>
</html>
Тест:
var win_in_frame = getFrameWindow(win, «frame_tab»)
selectDef(sel, text)
select(sel, text)
Выбирает опцию тега <select> по её тексту
sel – элемент. Получить его можно по имени: select(document.all.reason, «Вот так»)
<select name=«reason»>
<option value=1>А вот и нет
<option value=2>Вот так
</select>
Того же можно добиться и так: document.all.reason.value = 2
linkDef(win, text_in_link, [tag], [event], [number])
link(win, text_in_link, [tag], [event], [number])
возвращет ссылку на элемент с текстом text_in_link: <div>text_in_link</div> или <a>text_in_link</a> или в других элементах
- tag и event — параметры необязательные
- tag — теги, среди которых будет производиться поиск. Например: «input». По умолчанию – все ("*")
- event представляет собой строку кода
- event можно устанавливать в link.innerText, link.innerHTML, link.value, link.options[link.selectedIndex].text, link.title и т.п.
- number — номер найденного элемента. Так 1 (по умолчанию) — первый найденный, 2-й — второй найденный и т.д.
Пример:
Окно:
<html>
<head>
<script>
window.name=«Admin»
</script>
</head>
<body>
<div id=Div1>Пол<b>ь</b>зователи</div>
<div id=Div2>Пол<b>ь</b>зователи</div>
</body>
</html>
Тест:
win = getWindow(«Админ») // Получаем окно
div = link(win, «Пользователи», 2) // получаем второй элемент с текстом «Пользователи» в нём (Div2)
div.style.color = «red» // делаем его красного цвета
clickToLinkDef(win, text_in_link, [number])
clickToLink(win, text_in_link, [number])
нажимает на элемент указанный по контенту можно указать на какой по счёту элемент нажимать (number)
Пример:
Окно:
<a href="/users">Пользователи</a>
Тест:
clickToLink(win, «Пользователи»)
clickToButtonDef(win, text_in_value, [number])
clickToButton(win, text_in_value, [number])
нажимает на кнопку можно указать на какой по счёту элемент нажимать (number)
Пример:
Окно:
<input type=«button» value=«Пользователи»>
Тест:
clickToButton(win, «Пользователи»)
openLink(win, text_in_link, [sleepTime], [time], [number])
нажимает на элемент и возвращает объект одноимённого окна
- win — окно в котором нажимать
- text_in_link — текст в элементе
- sleepTime — сколько ждать до того как начать запрашивать окно. По умолчанию — 0 милисекунд
- time — время отпущенное на ожидание открытия окна. По умолчанию — 5000 милисекунд
- number — номер найденного элемента. По умолчанию — первый
Пример:
Окно:
<script src=«tests/WorkWithHtml.js»>
<script>
window.name = translate(«Пользователи»)
// window.name = «Polqzovateli»
</script>
Тест:
win = openLink(win, «Пользователи»)
openButton(win, text_in_value, [sleepTime], [time], [number])
нажимает на кнопку и возвращает объект одноимённого окна
- win — окно в котором нажимать
- text_in_value — название кнопки
- sleepTime — сколько ждать до того как начать запрашивать окно. По умолчанию — 0 милисекунд
- time — время отпущенное на ожидание открытия окна. По умолчанию — 5000 милисекунд
- number — номер найденной кнопки. По умолчанию — первый
Выводы
1. Недостатки Selenium-а:
а) требуется устанавливать сервер
б) требователен к ресурсам
в) много времени тратится на запуск сервера + запуск браузера
г) сервер Selenium-а перезапускает браузер для каждого теста
2. Недостатки jsUnit:
а) для каждого теста создаётся html-обёртка
б) для запуска нескольких тестов требуется написать новый тест с функцией suite(), в которой перечислить вызываемые
в) путь к тесту вводится в TestRunner (интерфейсе jsUnit) каждый раз вручную
г) отладочная информация выводятся в отдельное окно, а не в общий лог ошибок
3. Достоинства jsFUnit:
а) не требует сервера
б) не требователен к ресурсам
в) тест или группа тестов выбираются из выпадающего списка
г) группа тестов, которые нужно запустить просто кладутся в отдельный каталог
д) отладочная информация выводится в общий лог
е) можно визуально оценить количество выполненных тестов через закраску ползунка
Таким образом jsFUnit быстр, удобен в использовании и тем самым экономит ваше время!