Тестирование веб-проектов. 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 быстр, удобен в использовании и тем самым экономит ваше время!
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 31

      +1
      Используйте хабракат.
        +1
        Название тестовых функций жгут:)

        Но форматирование — вырви глаз.
          0
          Подправьте «играютавтоматезированные», пожалуйста.
            +1
            Так вам и надо — благодаря мне у вас разовьются новые чувства, призванные заменить устаревшее зрение!
              +1
              подправил.
                0
                После того как скачаете jsFUnit-2.1.zip, распакуйте его в public_html вашего веб-проекта. У вас появится каталог public_html/tests. Надеюсь, что все читатели осведомлены, что такое public_html. Но для тех, кто случайно начал читать статью, предлагаю взять виндовую машину, установить на неё xampp из www.apachefriends.org/en/xampp.html в c:\xampp и распаковать jsFUnit-2.1.zip в c:\xampp\htdocs.

                Надеюсь, все писатели осведомлены, что можно короче — DocumentRoot.

                time — необязательный параметр. Задаёт время ожидания загрузки страницы в фрейм. По умолчанию — 5000 микросекунд

                Нанотехнологии? Date::getTime() оперирует миллисекундами.
                  0
                  Интересно, сколько писателей прочитает мою статью?

                  Ну а так вы правы — это в милисекундах.
                  0
                  скорость выполнения тестов с селениумом сравнивали? было б интересно посмотреть на результаты.
                    0
                    В Selenium для каждого теста перезапускался браузер.

                    Задержка при открытии окна составляла на Seleron 2.66 ГГц/500 ОЗУ около 5 секунд.

                    В jsFUnit и jsUnit задержка составляет ок. 2 сек.
                      0
                      а зачем перезапускать браузер дял каждого теста?
                        –1
                        Так устроен Selenium. Надо бы спросить у его разработчиков.
                          0
                          а разве нельзя повесить браузер на глобальную переменную и во всех тестах ее использовать? или просто аттачить окно с браузером в каждом тесте?
                    +1
                    Скажите, а как тестировать более сложные юзкейзы, доступные для тестов, например, в том же Selenium?
                    «Функционального тестирования» в приведенном описании минимум (получить окно/фрейм, нажать кнопку, открыть ссылку и еще несколько). Аналогичным образом можно использовать тот же проверенный jsUnit, тем более когда в качестве селектора разработчик должен писать не сам селектор, а команду win.document.getElementById(...). В чем отличительные особенности вашей разработки от Selenium, где выигрышь? Скажем так, скорость выполнения тестов Selenium нормальная, выполнять тесты за 1 минуту и за 50 секунд обычно разницы большой не имеет, а вот функционал тестов — критический момент.
                      0
                      А что Вы подразумеваете под «функционалом тестов»? Приведенный функционал позволяет смоделировать любое поведение пользователя.

                      Я использую jsFUnit, работает быстро, гораздо быстрее Селениума, а главное, не требует ничего, кроме браузера.
                        0
                        Как пример, элементарный юзкейз: закрыть окно браузера, открыть новое окно, открыть тестируемую страницу, проверить сессию.

                        Как это можно осуществить с помощью описанной библиотеки, которая «не требует ничего, кроме браузера»?

                        «гораздо быстрее Селениума» — скорость чего вы имеете в виду?
                          0
                          > Как пример, элементарный юзкейз: закрыть окно браузера, открыть новое окно, открыть тестируемую страницу, проверить сессию.

                          С помощью библиотеки — никак, а используя библиотеку и уже встроенные возможности:

                          win.close() // закрываем окно
                          win = open("/login") // открываем в новом окне страницу

                          require(«translate.js») // подключаем модуль с функцией cookies
                          sleep(1000) // ждём пока загрузится translate.js

                          assetEquals( cookies().session, 'xxxx' ) // проверяем сессию
                            0
                            Повторюсь: «гораздо быстрее Селениума» в чем? И конкретно, для примера, который вы только что привели, выигрышь в скорости за счет чего при очень малом функционале библиотеки?
                            0
                            > закрыть окно браузера, открыть новое окно, открыть тестируемую страницу

                            Э… Простите, не понял. Что не так с этими командами? Просто берете и делаете это, соответствующие команды описаны в статье.

                            > проверить сессию.

                            Тут тоже не совсем понял. Сессия — понятие туманное. Как пользователь проверяет сессию (что бы вы под этим ни подразумевали)? Точно также и jsFUnit проверяет.
                          –1
                          Не могу с вами согласиться, что время затраченное на тест менее критично чем функционал.

                          Возьмём среднестатического разработчика.
                          Вот он пришёл на работу написал тест и запустил его. Тест выдал ошибку. Разработчик написал код. Запустил опять тест.

                          Пока тест выполняется разработчик пишет другой тест или код.

                          Но вот прошло пару часов. Близится время обеда. Внимание разработчика рассеевается…

                          И уже так легко переключаться между задачами не удаётся. Разработчик сидит и ждёт пока выполнится тест.

                          Допустим, что у разработчика много мелких задач и он пишет код 1 минуту, а затем запускает тест.

                          Рассеяно внимание у разработчика 4 часа в день.

                          Тест выполняется 1 минуту.

                          Таким образом разработчик теряет четверть рабочего времени (половину вышеуказанных 4 часов).

                          А вот если бы тест выполнялся 50 секунд, то разработчик сэкономил бы в день:

                          4*60*60/(60+50) ~ 131 раз за эти 4 часа разработчик запустит тест для 50 сек
                          4*60*60/(60+60) = 120 раз для 60 сек
                          ((60+50)*131 — (60+50)*120)/60 ~ 20 минут

                          За неделю — час двадцать
                          За месяц — 7 с половиной часов — почти рабочий день!

                          Согласен — время тратится и на написание тестов. Так что логичнее было бы их, как в selenium, нащёлкивать.
                          Но jsFUnit будет развиваться и в этом направлении без привязки к браузеру. Selenium же привязан к мозилле.

                            0
                            Кто на ком стоял? Потрудитесь излагать свои мысля яснее (с).

                            Какая-то вода о среднестатистическом разработчике в жидком вакууме тесте на 2 часа в 1 минуту.

                            Что касается нащелкивания, то упаси вас Столлман, Selenium кроссбраузерен. Selenium IDE — да, но никто не мешает писать тесты руками.
                              –1
                              Тогда разницы нет — что selenium, что jsUnit
                              0
                              «Возьмём среднестатического разработчика»

                              тогда я возьму jsUnit/Selenium. Вопрос закрыт.
                                –1
                                В jsUnit вам придётся писать руками путь к тесту.

                                А в jsFUnit выбор теста — через выпадающий список.

                                В jsUnit в случае хоть одной ошибки весь ползунок закрашивается в красный цвет.

                                В jsFUnit — в красный цвет закрашивается толко часть ползунка отвечающая за этот тест.
                                  0
                                  Если цвет ползунка и путь руками — основные достоинства библиотеки, то выбирать не приходится.
                                  А серьезные приемочные/функциональные тесты пишутся руками всегда, да, на Selenium RC.
                                    0
                                    Судя по вашему высказыванию на других движках пишут одни дилетанты. А профессионалы выбирают жутко медленный Selenium :)

                                      –1
                                      Главное преимущество — скорость.
                                        0
                                        В вашем примере выше о проверке кукисов скорость будет играть колоссальную роль :)
                                          –2
                                          Ошибаетесь! Пример-то выбирали вы! :-)
                                            –1
                                            И даже при проверке кукисов скорость важна. Селениум выполнить эту проверку не в пример медленнее.

                                            Если у Вас одни только кукисы проверяются — тогда, конечно, Вы не заметите разницы. Но если у Вас большое количество тестов, то замедление в несколько раз при использовании Селениума будет очень наглядно.
                                +3
                                Для начала неплохо, но есть несколько настоятельных советов: 1) уберите разноцветные jsFUnit — воспринимается как навязчивая реклама, приведёт к минусам только за это.
                                2) приведите цветовое форматирование и отступы к общепринятым нормам (обычно ещё в blocquote оборачивают код, тогда он не распадается), иначе наберёте за него минусов.
                                3) спеллчекер используйте — ошибок очень много (милисекунд, завершиться).
                                4) > document.all.reason.value — Вы по какому учебнику учили javascript? В курсе, что это некроссбраузерно?
                                  –2
                                  4) > document.all.reason.value — Вы по какому учебнику учили javascript? В курсе, что это некроссбраузерно?

                                  В курсе. Для того, чтобы старый код с all был кроссбраузерным применяют такую функцию:

                                  <script>
                                  function set_document_all() {
                                  var elements = document.getElementsByTagName("*")
                                  var i
                                  for(i=0; i<elements.length; i++) {
                                  var element = elements.item(i)
                                  if(element.name) document.all[element.id] = element
                                  }

                                  for(i=0; i<elements.length; i++) {
                                  var element = elements.item(i)
                                  if(element.id) document.all[element.id] = element
                                  }
                                  }
                                  </script>

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое