- PVSM.RU - https://www.pvsm.ru -
Одним дождливым осенним вечером пришла мне в голову мысль о том, что никогда прежде я не писал JavaScript код следуя канонам test-driven development (TDD). Лиха беда начало! Результатом работы стала маленькая библиотека-шаблонизатор работающая по принципу «JSON на входе, HTMLElement или просто DOM объект на выходе».
Из инструментов использовались: CoffeeScript [1], QUnit [2], PhantomJS [3], Google Closure compiler [4], а собирается всё это с помощью старого доброго GNU Make [5]. Статья для всех, кому интересна библиотека и для тех, кто поверхностно знаком с вышеперечисленными технологиями и хотел бы увидеть их в работе.
elem = kidomi(
['div#main.content',
['span', {style: {color: 'blue'}}, 'Select file'],
['form', {
name: 'inputName',
action: 'getform.php',
method: 'get'},
'Username: ',
['input', {'type': 'text', 'name': 'user'}],
['input', {'type': 'submit', 'value': 'Submit'}]]])
Где elem
— это объект HTMLElement
, который выглядит как:
<div id="main" class="content">
<span style="color: blue;">Select file</span>
<form name="inputName" action="getform.php" method="get">
Username:
<input type="text" name="user"></input>
<input type="submit" value="Submit"></input>
</form>
</div>
Ещё один пример, в котором сначала создаётся элемент <a>
, к onclick
которого привязывается функция, после чего элемент добавляется в общую структуру:
button = kidomi(['a.button', {href: '#'}]);
button.onclick = function() { alert('Hello world!'); };
elem = kidomi(['div', ['span', 'Click this button:'], button]);
Бывалые люди сразу вспомнят jquery-haml [6], однако вдохновение для написания kidomi черпалось из ClojureScript-библиотеки dommy [7].
Исходный код начинается следующим образом:
window['kidomi'] =
kidomi = (data) ->
...
Для тех, кто не знаком с особенностями компиляции CoffeeScript: по-умолчанию весь скомпилированный код оборачивается в функцию-обёртку и таким образом не экспортируется глобально. Кстати это поведение можно отключить флагом компилятора --bare
, но разве позволительно засорять глобальное пространство имён?
(function() {
/* ... */
window['kidomi'] = kidomi = function(data) {
/* ... */
}
/* ... */
}).call(this);
Ещё одна особенность записи:
window['kidomi'] =
# а не
window.kidomi =
Это сделано специально для компилятора Google Closure, который бы «сократил» название, при записи window.kidomi =
Далее, код пишется в похожем ключе:
kidomi.makeElementFromTagData =
makeElementFromTagData = (tagData) ->
# ...
kidomi.addAttributes =
addAttributes = (elem, data) ->
# ...
# и т.д.
Как вы могли заметить, функции объявляются как локально, так и «экспортируются» в функцию-объект kidomi
. В первом случае это сделано для удобства: не нужно писать никаких префиксов (хотя в CoffeeScript достаточно написать @name
, что скомпилируется в this.name
). A чтобы юнит тесты могли до этой функции добраться, её можно записать в виде аттрибута глобального объекта. Что и делается через kidomi.functionName
.
Помните свои первые шаги в TDD? Мне например стоило неимоверных усилий заставить себя сначала писать тесты, а после — код. Зато, как быстро TDD приносит дивиденды!
Как было сказано выше, для написания юнит тестов для kidomi использовалась библиотека QUnit [8]. Один из простейших тестов выглядит следующим образом:
test('isString', ->
ok(kidomi.isString(''))
ok(not kidomi.isString({}))
ok(not kidomi.isString([]))
ok(not kidomi.isString(10)))
А вот и сама функция:
kidomi.isString =
isString = (s) ->
typeof(s) == 'string' or s instanceof(String);
Стоит обратить внимание на то, что необходимо протестировать не только kidomi.js, но и обработанную напильником компилятором Closure kidomi.min.js. В идеале — все тесты покрывающие несжатый файл должны работать и для сжатой версии. Но тут мы натыкаемся на то, что все имена фунцкий кроме kidomi
были изменены до неузнаваемости. Например, вышеприведённый код isString(s)
превратился в
d.e=k=function(a){return"string"===typeof a||a instanceof String};
Чтобы с этим справиться, нужно скомпилировать библиотеку и тесты как единое целое. Также нужно указать компилятору, что qunit.js — это внешняя зависимость и соответственно такие имена фунцкий, как test
, module
, ok
и т.д. должны остаться без изменений.
Тем не менее, тестирование, в котором библиотека и тесты слиплены в один min.js файл, всё-таки отличается от тестирования сжатой библиотеки отдельно. Один из вариантов — запустить тесты лишь для основной функции.
Таким образом полное тестирование kidomi происходит в 3 прохода:
kidomi()
прогоняются на сжатой kidomi.min.js. Тесты и библиотека в отельных файлах.Как уже рассказывалось на Хабре [9], PhantomJS — это WebKit работающий в консоли и управляющийся собственным JS-API. На просторах интерета был найден скрипт [10] связывающий PhantomJS и QUnit простым и в то же время эффективным способом: он парсит страницу с результатами тестирования и завершает процесс с кодом 0 (успех) или 1 (ошибка) в зависимости от результата тестов. Кстати, все тесты можно запустить в обычном браузере.
Для сборки можно было использовать Rake [11], Maven [12], Grunt [13] и т.д., но к сожалению со всеми вышеперечисленными системами я на «вы» (камрады, обещаю наверстать упущенное к следующему посту на тему JavaScript). Make же, как мне кажется, справился с задачей на «Ура!».
Makefile состоит всего из трёх основных целей сборки (build targets): ${BUILD_DIR}, $(BUILD_DIR)/kidomi.js
и $(BUILD_DIR)/kidomi.min.js
(также дополнительные плюшки в виде целей all, clean, .PHONY
и т.д.). В конце Makefile'а подключается файл Makefile.testsuite.mk
содержащий цели и правила для сборки и запуска всех ранее упомянутых тестов.
Надеюсь, статья была для вас интересной и вы узнали из неё что-то новое. Исходный код kidomi [14] открыт для всех желающих. Архивы [15] содержат собранную версию библиотеки. Все комментарии, советы, отзывы и критика горячо приветствуются!
Благодарю за внимание!
Автор: BasicWolf
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/49762
Ссылки в тексте:
[1] CoffeeScript: http://coffeescript.org/
[2] QUnit: http://qunitjs.com/
[3] PhantomJS: http://phantomjs.org/
[4] Google Closure compiler: https://developers.google.com/closure/compiler/
[5] GNU Make: http://www.gnu.org/software/make/
[6] jquery-haml: https://github.com/creationix/jquery-haml
[7] dommy: https://github.com/Prismatic/dommy
[8] QUnit: http://habrahabr.ru/post/83170/
[9] рассказывалось на Хабре: http://habrahabr.ru/post/116789/
[10] скрипт: https://gist.github.com/gmarik/1305062
[11] Rake: http://habrahabr.ru/post/148167/
[12] Maven: http://habrahabr.ru/post/130419/
[13] Grunt: http://habrahabr.ru/post/148274/
[14] kidomi: https://github.com/BasicWolf/kidomi
[15] Архивы: https://github.com/BasicWolf/kidomi/releases
[16] Источник: http://habrahabr.ru/post/204506/
Нажмите здесь для печати.