Node.js: Обзор технологий разработки библиотек общего назначения
В этом посте я хочу обобщить и поделится полученным опытом при разработке библиотеки node-queue-lib. Я расскажу о технологиях, которые помогли мне довести дело до финального конца — работоспособного кода, который уже работает на одном из моих сервисов. Особенностью данной библиотеки является кросс-платформенный клиент, т.е. клиент работающий в node.js и браузере и основан на одном и том же коде. В посте будут описаны следующие инструменты, без которых разработка этой библиотеки превратилась бы в ад: Тестирование (jasmine_node) Покрытие кода тестами (istanbul) Сборка клиенткой части библиотеки (browserify) Автоматизированное тестирование клиента (phantomjs) Поиск утечек памяти (memwatch) Если Вы об этом ещё ничего не слышали и имеете желание написать законченный продукт в надёжности которого Вы будете уверены, эта обзорная статья поможет Вам познакомится с одним из вариантов комплекта инструментов для полноценного контроля качества кода javascript библиотеки.И дополнительно, повторю, что статья обзорная, и не ставит целью научить Вас виртуозно пользоваться всеми перечисленными инструментами. Я лишь покажу дверь, но откроете Вы её сами…
Все примеры будут содержать код для системы сборки grunt. Если Вы ещё не знакомы с тем, что это такое, самое время это сделать, иначе на пути к успеху вы будете регулярно спотыкаться о консоль. Если Вы «продвинутый», не кричите в комментариях «gulp», адаптировать код для вашей системы сборки для Вас не составит проблем. Библиотек для тестирования в настоящее время предостаточно, чтобы сделать выбор. И вы должны его сделать и выбрать понравившуюся Вам модель тестирования. Я не навязываю Вам используемую мною библиотеку, потому что главное — это иметь тесты, а выбор библиотеки второстепенен. Мой выбор в данном случае был сделан по следующим причинам: Некоторые исторические моменты, в которые я уже использовал jasmine. Поддержка node.js и браузера одной библиотекой, поскольку имеется переиспользование кода. Зрелость библиотеки, и как следствие, наличие интеграции в различных инструментах. Установка
npm install grunt-jasmine-node --save-dev Gruntfile.js конфигурация
jasmine_node: { options: { forceExit: true, match: '.', matchall: false, extensions: 'js', specFolders: ['./spec/'] }, all: ['spec/'] } Как писать тесты я Вас не буду учить, такого материала предостаточно. Лучше я перейду ко второму инструменту, цель которого показать, что Вы не только пишете тесты, но и какие участки кода ваши тесты тестируют. Ведь совершенно бессмысленно писать тесты, не представляя общей картины протестированного кода.
Нет ни одной уважительной причины, чтобы код библиотеки не был покрыт тестами на 100%. Каждое условие в коде должно быть покрыто тестом, иначе Вы столкнётесь с непредвиденным поведением или напишите код, который никогда не работает. Если код не покрыт тестами на 100% доверие к библиотеке падает. Чтобы проверить покрытие кода тестами необходимо воспользоваться библиотекой istanbul. Сам по себе istanbul в «голом» виде нам не интересен. Необходимо его интегрировать в систему запуска тестов. Этим и занимается модуль grunt-jasmine-node-coverage. Он позволит Вам получить отчёт в html-виде (и не только) для комфортного анализа покрытия исходного кода.Установка
npm install grunt-jasmine-node-coverage --save-dev Поскольку мы интегрируем jasmine с istanbul, необходимо всего навсего изменить конфигурацию jasmine
jasmine_node: { coverage: {}, // включаем анализ покрытия кода options: { forceExit: true, match: '.', matchall: false, extensions: 'js', specFolders: ['./spec/'] }, all: ['spec/'] } Кстати, анализ покрытия кода тестами заставил меня немного по другому взглянуть на некоторые фрагменты. Например, из анализа некоторых участков кода я выявил, что не определено поведение функции в некоторых конструкциях if. Проще говоря не было блока else. Рефакторинг одной из таких функций привёл меня к пониманию, что весь класс и взаимодействие с ним написано неправильно и было переписано приличное количество кода. Так же было выявлено довольно много слабых мест при контроле ошибок. Где-то их не было вообще, где-то слабо проверялось ну и т.д. Так что пользуйтесь анализом покрытия кода — это реально заставит Вас посмотреть на свой код совершенно иначе. Тем более, что использование инструмента такое простое, а отчёты построены совершенно предсказуемо.
Этих инструментов вполне достаточно, чтобы быть уверенным что Вы контролируете качество своего кода. А дальнейший материал раскроет более сложные аспекты разработки библиотек.
Если Вы создаёте кросс-платформенную библиотеку (node.js и браузер) Вам понадобится инструмент для сборки клиентской части библиотеки. Чтобы превратить код для node.js в код, который можно будет подключить к браузеру, необходимо воспользоваться инструментом browserify.Установка
npm install grunt-browserify --save-dev Конфигурация
browserify: { dist: { files: { 'dist/my-library.js': ['lib/my-library.js'] } } } Конфигурация довольно понятная при условии, что в папке /dist у вас собираются финальные файлы, а в папке /lib лежат исходники. Не забывайте минимифицировать полученный файл с помощью uglify. Тема настолько заезжена, что я не буду её раскрывать в своей статье. На всякий случай оставлю только ссылку. Не нужно уповать на прозрачность, которую даёт технология из предыдущей главы. Протестируйте свой код в браузере. Особенно, если он использует внешние библиотеки подобные вашей, собранной с помощью browserify. Учитывая что jasmine имеется и под node.js и под браузер тесты можно переиспользовать. Просто добавьте в конфигурацию browserify файл с тестами и настройте jasmine на их запуск. Конфигурация будет выглядеть примерно так: browserify: { dist: { files: { 'dist/my-library.js': ['lib/my-library.js'], 'dist/my-library.spec.js': ['spec/my-library.spec.js'] } } } В моём случае я использовал довольно популярную библиотеку socket.io и при первом запуске интеграционных тестов в браузере обнаружил, что ни один тест не прошёл, поскольку я совершенно напрасно установил опцию socket.io, которая на каждый запрос создаёт новое соединение. Для браузера это было фатально и мне пришлось довольно прилично порефакторить, чтобы мой код под node.js заработал без вышеупомянутой опции. Тестируйте свой код в браузере после применения различных инструментов.
Итак, тестирование клиента обязательно, но оно пока не автоматизировано. jasmine предполагает открытие браузером html-страницы (так называемый TestRunner). Но нам необходимо чтобы тестирование проводилось в автоматическом режиме. Для этого нам поможет проект под названием phantomjs. Суть технологии в том, что это полноценный браузер, но только без UI. То есть вообще без какого либо UI, — графического или текстового. Управляется такой браузер простым скриптом на javascript запускаемым в phantomjs.
Установка
npm install phantomjs --save-dev Использование. Чтобы запустить наш TestRunner.html и получить результаты тестирования необходимо следующее:1. Написать файл спецификации node.js который запустит phantom.js (для автоматического запуска в jasmine)2. Написать скрипт для phantomjs, который будет делать следующее:
Дождётся окончания работы jasmine. Пропарсит DOM получившейся страницы и выяснит сколько тестов упало. (Есть подозрения, что есть лучший путь кроме как парсить DOM). Завершит работу phantomjs. 3. Проанализировать результат работы phantomjs на предмет сообщений о падении тестов.Вообще с этой частью придётся неплохо повозиться. Я прекрасно представляю, что человек, который первый раз слышит о технологии phantom.js вообще ничего не понял из выше приведённого списка. Это не беда. Нужно только открыть соответствующие страницы документации на phantomjs, почитать и вникнуть. И стоит только освоить это технологию, перед вами открываются довольно хорошие перспективы по автоматизации тестирования UI.Для вдохновения можно посмотреть на уже реализованный сценарий в вышеупомянутой библиотеке, на опыте разработки которой появилась эта статья.
Ссылки на файлы для вдохновения
Ну и теперь, когда ваш код протестирован на 100%; протестирован в различных окружениях, с некоторыми небольшими оговорками конечно, вас должно вполне обоснованно распирать от гордости за своё детище. Но… есть на вашем пути ещё одно препятствие не преодолев которое Вам вероятнее всего отрубят пальцы.
Даже если Вы всё сделали правильно и Ваш код протестирован на 100% это не даёт Вам никакой гарантии того, что библиотека не «течёт». И если в браузере по объективным причинам на это забивали, то «текучий» серверный сервис как не трудно догадаться — это провал.Более менее рабочим инструментом для поиска утечек памяти который я смог найти — это memwatch (статья на хабре). К сожалению автоматизировать его использование у меня не получилось. Отсутствует стабильность результатов (или я что-то упустил).Как утверждается в самом memwatch дамп «хипа» снимается всегда после запуска garbage collector`а. Но тем не менее всё равно в отчёте мелькают какие-то якобы не освобождённые объекты. Реальный тест с миллиардами итераций шаблонных test-case`ов показывал, что библиотека память не ест и работает стабильно.Но это, естественно, не аргумент против использования инструмента. Возможно в будущем он будет работать стабильно. Даже надеюсь, что это не его проблемы, а node.js. И в новых версиях он может показать лучшие результаты. Кстати, с помощью данного инструмента я обнаружил, что socket.io не течёт. И нашёл приличное количество утечек в своём коде. И утечки эти были исключительно в использовании одного и того же класса: EventEmitter. В принципе про это много написано, что EventEmitter — «дары приносящий…». Но как говорится, — опыт бесценная вещь. Приобретайте его, пользуйтесь инструментами автоматизации в своей работе и мы увидим много качественных работ.