- PVSM.RU - https://www.pvsm.ru -
Картинка для привлечения внимания:

Я — начинающий веб-разработчик. И не так давно мне захотелось научиться работать так, как это делают настоящие программисты.
Под этим я понимал 3 основных элемента:
Для первого пришлось освоить азы git [1], и создать свой первый репозиторий на github [2]. Для второго выбрал JsDoc [3], из-за которого пришлось перебраться с notepad++ [4] на sublime text [5] (только там был соответствующий плагин).
А вот с третьим, неожиданно для меня, возникли серьёзные трудности.
Так как я очень уважаю jQuery [6] (кстати первый свой репозиторий я как раз и открыл для написания плагина для jq) выбор фреймворка для юнит-тестирования пал на Qunit [7] что оказалось роковой ошибкой. И столкнулся со следующим:
Мне всё это весьма не нравилось, но осознание того, что большие дяди и тёти так работают, не давало мне опустить руки. Я надеялся, что когда завершу разработку своего плагина, мне откроется какой-то сакральный смысл всего того, с чем мне приходилось сталкиваться во время его тестирования. Неделю я кололся, плакал, но продолжал есть кактус, сражался с тяжёлой формой болезни я-знаю-как-надо. Но мои нервы не железные и, в конце концов, я расплакался как маленькая девочка сдался и написал свой велосипед.
test.it — фреймворк для тестирования JavaScript кода.
Для тех, кому текст не интересен — ссылка на репозиторий на github: test.it [8]
Основные особенности:
Последнее я подглядел у Qunit, в этом они, конечно, молодцы.
На текущий момент реализованы тесты на:
Для того что бы подключить фреймворк — достаточно всего лишь добавить строку
<script src='path/to/testit.js'></script>
куда вам вздумается в конец тега <body>.
А начать использование можно, очевидно, с первого теста:
test.it('first test');
Тест — функция (метод объекта test) которая проверяет выполнение какого-либо условия.
Функция test.it(entity) проверяет существование entity и не-ложность его значения.
Открыв консоль (Firebug или Chrome, как обладающие максимальной поддержкой данного API [11]) мы увидим следующее:

Не густо (:
А всё потому, что мы не завершили тест вызовом test.done(). Сделаем это. Теперь наш код выглядит так:
test.it('first test');
test.done();
А в консоли соответственно:

Что означает, что все тесты пройдены.
root — корневой элемент, или группа тестов нулевого уровня. Он обладает теми же свойствами что и любая другая группа, но о них будет подробнее чуть позже.
Если раскрыть элемент root мы увидим статистику прохождения всех тестов внутри него, и время их выполнения.

Очевидно что:
Статистика разделена на тесты и группы. Но наверняка в ближайших релизах она упроститься, и это разделение пропадёт.
Последняя строка: pass: no comment — наш тест. Развернём его и посмотрим подробнее:

Первым делом идёт метка о том, что тест пройден. Вообще они бывают трёх видов:
Далее no comment — комментарий, который мы ещё не задали. Чуть ниже рассмотрим, как это можно сделать.
Следующая строка «argument exist and not false» — описание того, что необходимо для прохождения теста.
И напоследок массив полученных аргументов, в нашем случае из одного элемента ″first test″
Что бы не видеть больше этого неприятного no comment, добавим к нашему тесту комментарий.
test.it('first test');
test.comment('Простая проверка');
test.done();
Теперь результат выглядит так:

Но все что мы только что писали, в принципе, ничего особо не тестировало. Давайте исправим ситуацию и добавим настоящий тест.
test.it('first test');
test.comment('Простая проверка');
var Me = {name:'Titulus',lastName:'Desiderio'};
test.it(Me);
test.comment('Я существую?');
test.done();
Теперь в консоли:

Можем рассмотреть второй тест поподробнее. Развернём его, и Object — единственный аргумент, в массиве аргументов.

Как вы видите, тест проверяет всего лишь существование, и не-ложность значения единственного переданного ему аргумента. Но это не мешает иногда подглядывать в этот объект, что бы лишний раз напомнить себе, что именно мы передали, и получить дополнительную, полезную в некоторых случаях, информацию.
Предыдущие тесты были успешно пройдены, давайте поставим задачу посложнее.
test.it('first test');
test.comment('Простая проверка');
var Me = {name:'Titulus',lastName:'Desiderio'};
test.it(Me);
test.comment('Я существую?');
test.it(Me.habr);
test.comment('Хабр?');
test.done();

Как вы заметили, проваленный тест, и соответствующая проваленная группа (root) были раскрыты по умолчанию.
Кстати, благодаря массиву аргументов, мы сразу видим, почему тест был провален — переданный аргумент не определён.
Теперь осталось исправить ситуацию:
test.it('first test');
test.comment('Простая проверка');
var Me = {name:'Titulus',lastName:'Desiderio'};
test.it(Me);
test.comment('Я существую?');
Me.habr = 'Хабрахабр!';
test.it(Me.habr);
test.comment('Хабр?');
test.done();
И вновь старая картина!

Давайте ещё усложним задачу. Вместо проверки на существование и не-ложность, проверим на правильность значения.
test.it('first test');
test.comment('Простая проверка');
var Me = {name:'Titulus',lastName:'Desiderio'};
test.it(Me);
test.comment('Я существую?');
Me.habr = 'Хабрахабр!';
test.it(Me.habr);
test.comment('Хабр?');
test.it(Me.habr,'habrahabr.ru');
test.comment('адрес хабра');
test.done();
функция test.it(entity1, entity2) — проверяет равенство между entity1 и entity2.
Посмотрим в консоль:

Тест провален, чего и следовало ожидать. Глядя на результат, причина провала вполне очевидна. Исправив
Me.habr = 'Хабрахабр!';
на
Me.habr = 'habrahabr.ru';
Мы опять получаем

Разберёмся в упомянутых ранее группах.
Группа —
Рассмотрим следующий код:
test.it(2>1);
test.comment('Работают ли тут законы математики?');
test.group('первая группа',function(){
test.it(2>1);
test.comment('А тут?');
});
test.done();
С test.it(2>1); и так всё понятно, но что делает test.group?
Функция test.group(groupname, fun) — создаёт новую подгруппу для тестов, других групп и прочего кода. Имя берётся из аргумента groupname. А функция fun будет пытаться выполниться, и если внутри неё произойдёт ошибка — это не прервёт работу остального кода. Ошибка будет помещена в поле error данной группы.
Раскроем root:

Вот она наша группа первая группа, оформлена так же как root, только имя то — что мы задали.
Раскроем и её.

Никаких особых отличий от root нет и быть не должно.
Тут стоит только обратить внимание на статистику в root и в нашей группе.
! Важный момент: статистика отображает только результаты на данном уровне. Пусть у нас и написано 2 тест, но у root в статистике тестов виден только один пройденный.
Добавим туда ещё пару-тройку тестов.
test.it(2>1);
test.comment('Работают ли тут законы математики?');
test.group('первая группа',function(){
test.it(2>1);
test.comment('А тут?');
test.it(1, Number(1));
test.comment('равна ли еденица самой себе');
test.it(h.a.b.r);
test.comment('А что ты сделаешь с несуществующим аргументом?');
test.it(2+2,4);
test.comment('проверим на знание школьного курса');
});
test.it(1<2);
test.comment('А теперь?');
test.done();
И увидим

что тест на 2+2=4 даже не был запущен, потому что предыдущий с h.a.b.r не был выполнен из-за ошибки ReferenceError, которая была аккуратно выведена под статистикой, до тестов. Но при этом последний тест на 1<2 — проходит, потому что он был вне группы, с ошибкой.
Обработка ошибок — одна из приоритетных задач, которую я перед собой на сегодняшний день ставлю. Так что не удивляйтесь, если данный пример потеряет свою актуальность после ближайших релизов. Но основная идея отлова ошибок, без аварийного завершения программы, останется.
И последний момент касательно групп. Многоуровневая вложенность!

test.group('need',function(){
test.group('to',function(){
test.group('go',function(){
test.group('deeper',function(){
test.it('bye habr');
test.comment('bye bye');
});
});
});
});
test.done();

Весь код доступен на github [8], так что можете его читать, комментировать, форкать, предлагать пуллреквесты и т.п. Лицензия MIT [12], хотя подумываю о переходе на WTFPL [13].
Хотел остановиться подробнее на test.root — объект соответствующий группе нулевого уровня. Именно его заполняют тесты, а потом уже _printConsole() его парсит и наводит всю эту красоту.
{
"type": "group",
"name": "root",
"status": "pass",
"time": 7,
"result": {
"tests": {
"passed": 1,
"failed": 0,
"error": 0,
"total": 1
},
"groups": {
"passed": 1,
"failed": 0,
"error": 0,
"total": 1
}
},
"stack": [
{
"type": "test",
"status": "pass",
"comment": "Работают ли тут законы математики?",
"description": "argument exist and not false",
"time": 0,
"entity": [
true
]
},
{
"type": "group",
"name": "первая группа",
"status": "pass",
"time": 1,
"result": {
"tests": {
"passed": 1,
"failed": 0,
"error": 0,
"total": 1
},
"groups": {
"passed": 0,
"failed": 0,
"error": 0,
"total": 0
}
},
"stack": [
{
"type": "test",
"status": "pass",
"comment": "А тут?",
"description": "argument exist and not false",
"time": 0,
"entity": [
true
]
}
]
}
]
}
Кстати да. мне очень стыдно, но есть часть стороннего кода — функция deepCompare() не доступная извне. Она сравнивает два аргумента любых типов. Взял её тут [14].
И большой спасибо mkharitonov [15] за подсказку в реализации многоуровневой вложенности.
Конечно, есть недостатки. Некоторые серьёзные, некоторые не очень. Надеюсь, раз код opensource, сообщество поможет мне их минимизировать, или превратить в достоинства.
Ключевые:
А дальше будут названные выше изменения вывода статистики.
Улучшения обработки и вывода ошибок.
Улучшения вывода тестов — добавятся нумерация и подсказки для проваленных тестов.
Добавятся новые тесты test.them, test.type, test.types, test.time. О них подробнее можете прочитать в README [16].
Будут исправляться недостатки, названные в предыдущем разделе.
Ещё раз ссылка на репозиторий на github: test.it [8]
P.S. Пост будет перенесён в хаб «Я Пиарюсь», как только наберу достаточно кармы.
Автор: titulusdesiderio
Источник [17]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/39870
Ссылки в тексте:
[1] git: http://ru.wikipedia.org/wiki/Git
[2] github: https://github.com
[3] JsDoc: http://ru.wikipedia.org/wiki/JSDoc
[4] notepad++: http://notepad-plus-plus.org/
[5] sublime text: http://www.sublimetext.com/
[6] jQuery: http://jquery.com/
[7] Qunit: http://qunitjs.com/
[8] test.it: https://github.com/titulus/testit
[9] ok: http://api.qunitjs.com/ok/
[10] equal: http://api.qunitjs.com/equal/
[11] API: http://habrahabr.ru/post/188066/
[12] MIT: https://ru.wikipedia.org/wiki/%D0%9B%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D1%8F_MIT
[13] WTFPL: http://ru.wikipedia.org/wiki/WTFPL
[14] тут: http://stackoverflow.com/a/1144249/1771942
[15] mkharitonov: http://habrahabr.ru/users/mkharitonov/
[16] README: https://github.com/titulus/testit/blob/master/README.RU.md
[17] Источник: http://habrahabr.ru/post/188268/
Нажмите здесь для печати.