![image](https://habrastorage.org/getpro/habr/post_images/34d/7c3/beb/34d7c3beb48e07860fe13ad7f1d3b7e7.jpg)
Я не буду предлагать вам очередной модный фреймворк для тестов, а просто покажу подход к тестам и документации, который использую в проектах, разрабатываемых в i-Free. Возможно, вам понравится, и вы начнете организовывать проекты таким же образом или укажите мне на явные проблеммы.
Многие веб-разработчики не любят писать тесты, и я не исключение. Но тесты уменьшают количество багов и если ваше приложение становится все больше и больше, от тестов вам не уйти. К тому же в мелких компаниях я часто встречал джуниоров, которые вообще предпочитают писать код в текстовых редакторах (это увеличивает количество ошибок, т.к. редакторы не проверяют код). Как же начать потихоньку использовать тесты без боли и страданий?! Выход есть — подключить автотесты.
Суть поста на картинке слева. Это то, чего мне раньше не хватало в повседневной работе. Хотелось иметь инструмент, которым можно очень просто потыкать в код и сделать общее заключение о его живучести и пригодности.
Авто-тесты
Проблемы:
- Разработчику лень писать сами тесты
- Разработчику лень писать лишний код для тестов
- Разработчику вообще лень думать про тесты
Решение: Подключиться к приложению через API какого-либо фреймворка, который разработчик использовал, и прогнать авто-тесты через него.
Куда подключаться:
- Функции навешивания событий (типа $("#button).click(), core.addEvent(), helper.onClick() и т.п.)
- Функции слушателей (типа $("#button).on(), event.listen(), mediator.listen() и т.п.)
- Функции инициализации (типа $(body).ready(), utils.ready() и т.п.)
- Функции callback`и при AJAX запросах
Что проверять:
- Работоспособность при некорректных аргументах
- Наличие случайных багов из-за опечаток (вроде пропущенной ;)
- Наличие всех DOM элементов необходимых скрипту
Можно вызывать все подряд функции в try / catch с кучей случайных параметров и без них. Смотреть какие из функций отвалятся. Кроме того пробежаться по всем ссылкам и запросам DOM элементов и проверить их наличие в верстке.
Что это нам дает:
Понятно, что это достаточно бесмысленное тестирование с точки зрения логики веб-приложения. Но с другой стороны мы можем без лишних усилий и на скорую руку перепроверить сборку от совсем уже глупых опечаток и нелепостей.
Бонусы:
- Можно утверждать, что таким автотестом можно покрыть 90% функций приложения
- Такой тест не требует абсолютно никаких усилий от разработчика в плане поддержки, обновления и т.п. Грубо говоря он может вообще не думать о них
- Баги все равно будут, поэтому лишней проверка не бывает
- Чтобы провести тест нам не надо вообще менять структуру нашего приложения
Ещё раз повторюсь:
- Это не отменяет юнит-тесты!
- Это бессмысленно с точки зрения логики приложения!
- Тесты ради тестов — плохо
Но! Это нахаляву и без усилий, и это лучше, чем ничего. Если вы покажете это вашим джуниорам, они убедятся, что это на самом деле просто и без боли, а значит будут более лояльно относиться к тестам, а значит быстрее прийдут к мысле о написание юнит-тестов.
Схема работы:
![image](https://habrastorage.org/getpro/habr/post_images/b72/36e/bbb/b7236ebbbde62b19eb363ab45ad21a11.png)
У нас есть наше приложение, состоящие из кучи модулей. Чтобы проверить его работу нам нужно для каждого модуля написать юнит-тест. Хорошо если мы это сделаем, но, по факту, большинство разработчиков не будет тратить на это время, особенно в небольших приложениях. Так же если взять какое-либо готовое решение для тестирования, требующее изменения кода модулей — этого никто делать не будет. С другой стороны, вставить пару функций в прослойку между библиотеками и приложением очень легко (если у вас такой прослойки нет — можно вставить эти функции в саму библиотеку). И на выходе мы уже получаем хоть какое-то тестирование (можно назвать это тестированием «от дурака», который будет тыкать все подряд кнопки). Ну а если у вас все-таки была прослойка — вообще замечательно. А ещё можно сделать подмену функции AJAX запросов.
Если у вас код разбит на модули и оформлен в виде:
(function(global) {
var module = {
_methodA: function() {},
_methodB: function() {},
_methodC: function() {},
init: function() {}
}
module.init();
})(this);
или аналогичных конструкций — тогда на опыты можно экспортировать сразу весь модуль.
У себя я завел объект test и все, что можно — начал пихать в него:
test.add(); // ждет на входе объекты типа модулей
test.addFunction(); // ждет на входе функции
А дальше все просто. Внутри этого теста есть несколько массивов, в которые собираются куча callback`ов и модулей. На выходе есть ещё один метод:
test.start();
В этот момент запускается проверка всего того, что насобиралось в массивы. И это все проверяется в try/catch конструкции. Если кто-то дохнет, в консоли выскакивает уведомление и из массива берется следующая жертва.
Если мы проверяем функции — то, кроме простого вызова, они также вызываются с параметрами. По сути идет перебор от нуля до 4х параметров. Каждый параметр по порядку принимает ряд значений (-1, 0, 1, «string», true, false, [], {} и т.д.)
![image](https://habrastorage.org/getpro/habr/post_images/ed4/ed7/119/ed4ed7119a249708e49200a87f282bfc.png)
Если мы берем модуль, то это объект. Пробегаем по свойствам объекта, и если натыкаемся на функцию — проверяем её по алгоритму выше. Т.к. у всех моих модулей одна и таже структура, можно проверить ещё несколько моментов. Например, узнать все ли DOM элементы были найдены/созданы. Ссылки на них хранятся в свойстве _nodes, которое есть почти у всех модулей. Например:
var module = {
_nodes: {
table: DOM_element,
link: DOM_element
},
_methodA: function() {},
_methodB: function() {},
_methodC: function() {},
init: function() {}
}
Если, пробегаясь по объекту module._nodes, мы внезапно обнаруживаем null — значит что-то помешало модулю. Возможно, инициализация не отработала, или в DOM структуре страницы пропали какие-то элементы. Сразу сделаю оговорку, что ссылку на элемент я дергаю только один раз. Все остальное время она хранится внутри модуля и вместо:
$("#name").html("Ваня");
у меня будет:
var node = this._nodes.name;
if(node) node.innerHTML = "Ваня";
![image](https://habrastorage.org/getpro/habr/post_images/5a5/b1d/6f1/5a5b1d6f1515eabef26abd140551c518.png)
Стабы и Моки
Процитирую Сергея Теплякова:
Честно говоря, я не большой фанат изменений в дизайне только ради «тестируемости» кода. Как показывает практика, нормальный ОО дизайн либо уже является достаточно «тестируемым» или же требует лишь минимальных телодвижений, чтобы сделать его таковым. Некоторые дополнительные мысли по этому поводу можно найти в заметке «Идеальная архитектура».
Оригинал можно посмотреть тут http://habrahabr.ru/post/134836/
Если вы о них никогда не слышали, то стаб — это вроде сервера, или какой-то внешний объект-фальшивка, который выдает разные результаты при обращение к нему. Так сказать объект-заглушка для тестов. Моки — тоже самое, только ещё считают статистику, что и сколько раз дернули.
Нужноли менять что-то на сервере для тестов?
— Нет, мы просто поставим заглушку в месте AJAX запроса.
Нужноли менять что-то в месте AJAX запроса в коде приложения?
— Нет, мы можем изменить саму функцию запроса в библиотеке не трогая наш код. А следовательно не имеет значения где и сколько раз будет вызвана функция — она всегда будет дергать заглушку.
Например, у вас есть код:
$.ajax({
url: "ajax/test.html",
success: function(data) {
alert(data.message);
}
});
Вместо того, чтобы разбирать его для тестов и вытаскивать callback, лучше разобрать ajax. Мы снова заходим со стороны API библиотеки, не трогая наш код. Да, конечно разобрать jQuery или другую библиотеку и «воткнуть» в нее наши щупы непросто, но вы всегда можете написать свою тонкую прослойку между библиотекой и кодом. Это не только позволит вам пропихивать тесты без боли и делать подмену реальных объектов на заглушки, но ещё дополнительным бонусом вы получите возможность перекатываться с одного фреймворка на другой.
Понятно, что такие тесты хадкорны и могут показать только какие-то явные баги. Но это лучше чем ничего. К тому же они не требуют никаких усилий на их поддержку и написание. Запустили для галочки, убедились, что вроде все отработало, пишите дальше. Ещё один плюс таких автотестов — к ним можно приучить джуниоров, а когда они привыкнут и перестанут бояться тестов, начать писать нормальные юнит-тесты.
Авто-документация
Заставить себя писать документацию бывает трудно. Заставить писать документацию других — практически нереально. Но можно начать с малого, привести разумные доводы и дать инструменты, которые будут потихоньку помогать. В своих проектах в i-Free я добавил ещё одно решение. Как уже было сказанно выше, у всех модулей в конце есть test.add(); И после инициализации приложения получается некий массив, который хранит ссылки на все модули. Можно пробежаться по этому массиву, и, поскольку модули представляют собой объекты, составить их дерево. Например:
module
_methodA -> function
_methodB -> function
_methodC -> function
init -> function
А также получить информацию по статистике: список внешних методов, список внутренних методов и т.д. В самом простом случае нам надо всего лишь подписать это дерево:
module
_methodA -> Запускает рендер.
_methodB -> Обрабатывает результаты и отдает массив.
_methodC -> Запрашивает результаты у сервера.
init -> Инициализирует модуль.
Т.к. у нас так же есть доступ ко списку всех назначенных событий, мы можем их отфильтровать и получить списки того, что модуль слушает и что публикует. Пример того, как это выглядит в жизни:
![image](https://habrastorage.org/getpro/habr/post_images/f12/e80/3c0/f12e803c0b695f6b356bd50565b08c89.png)
По полученным логам можно кратко составить описание модуля:
![image](https://habrastorage.org/getpro/habr/post_images/5e8/dba/fae/5e8dbafae7c3d4ef9cf3f57b57d216be.png)
Таким разбором кода вы можете показать джуниору моменты, которые он должен описать в документации и пояснить в коде. А просмотр структуры модуля в коротком виде может показать вам и коллегам, что, возможно, некоторые методы объекта стоит поменять местами, т.к. их порядок несколько не логичен для понимания процесса.