Глава 2. Демонстрационное приложение и первые тесты
В этой главе мы приступим к разработке нашего приложения, которое мы будем использовать в качестве примера на протяжении всего учебника, и начнем с самого простого, а именно со статических страниц. Так же мы познакомимся с тестированием приложений на node.js и с инструментами, которые используются для этого.
2.1 Model-View-Controller (MVC)
Перед тем как приступать собственно к разработке приложения, полезно поговорить о том, что из себя представляет типичная архитектура web-приложения на наиболее высоком уровне абстракции. Самым популярным архитектурным паттерном на сегодняшний день является model-view-controller (MVC), общий смысл паттерна заключается в том, чтобы разделить бизнес логику приложения (её привязывают к моделям) и представление — view. Кроме того, модели реализуют интерфейс к базе данных. Контроллер играет роль посредника между моделью и представлением. В случае web-приложения — это выглядит так: браузер пользователя отправляет запрос на сервер, контроллер обрабатывает запрос, получает необходимые данные из модели и отправляет их во view. View получает данные из контроллера и превращает их в красивую HTML страничку, которую контроллер в итоге отправит пользователю.
2.2 Демонстрационное приложение
Пришло время приступить к разработке нашего демонстрационного приложения. В первой главе мы уже развернули тестовое приложение, но воспользовались при этом генератором express и не написали ни строчки кода. Теперь мы будем писать наше приложение сами и начнем с «Hello, World».
$ cd ~/projects/node-tutorial $ mkdir node-demo-app $ cd node-demo-app
2.2.1 Пакеты npm
Что такое npm? Все просто, это node package manager (хотя авторы это оспаривают). В общих чертах пакет npm — это директория содержащая программу и файл package.json, описывающий эту программу, в том числе в этом файле можно указать от каких других пакетов зависит наша программа, почитайте описание package.json.
Для того чтобы воспользоваться всеми прелестями, которые нам может предоставить npm, мы создадим в корневой директории нашего проекта файлик:
$ touch package.json
package.json:
{ "name": "node-demo-app" , "version": "0.0.1" , "scripts": { "start": "node server.js" } , "dependencies": { "express": "3.0.x" } }
Теперь можно выполнить
$ npm install
В результате npm создаст директорию node_modules в которую поместит все модули от которых зависит наш проект.
2.2.2 Hello, World!
Основной файл назовем server.js:
$ touch server.js
server.js:
var express = require('express') , app = express() , port = process.env.PORT || 3000 app.get('/', function (req, res) { res.send('Hello, World!') }) app.listen(port, function () { console.log('Listening on port ', port) })
Сразу определимся с терминологией и разберем этот код. Нашим приложением будет объект app
, вызов функции app.get
монтирует экшн (action), роль которого в данном случае выполняет анонимная функция, к пути (route) ‘/’. Фактически это означает, что каждый раз при получении http запроса GET /, приложение выполнит указанный экшн. Переменная port
в этом примере инициализируется переменной окружения PORT
при её наличии, а если такой переменной нет, то принимает значение 3000. app.listen
запускает http-сервер на указанном порте и начинает слушать входящие запросы.
Для того, чтобы полюбоваться результатом нашего труда, есть два способа:
$ node server.js
либо
$ npm start
Второй способ доступен потому что мы добавили соответствующую строчку в файл конфигурации package.json в разделе «scripts».
Теперь по адресу http://localhost:3000/ можно получить строчку ‘Hello, World!’.
Настало время залить что-нибудь в GitHub. Создаем новый репозиторий на GitHub с названием node-demo-app и выполняем в директории проекта следующий набор команд, сперва создадим файл README.md (правило хорошего тона)
$ echo '# Node.js demo app' > README.md
Создадим файл .gitignore для того чтобы не коммитить лишние файлы в git, а именно директорию node_modules:
$ echo 'node_modules' > .gitignore
Возможно кто-то читал статью Mikeal Rogers и хотел бы возразить против добавления node_modules в .gitignore. Для тех кому лень читать, в проектах на node.js рекомендуется такой подход:
- Для проектов которые мы разворачиваем, таких как веб-приложения, node_modules помещаются в репозиторий.
- Для библиотек и другого повторно используемого кода node_modules не добавляются в репозиторий.
- Для развертывания на production npm не используется.
Но! Мы в качестве хостинга используем Heroku и способ деплоя не выбираем, а там node.js проекты деплоятся с помощью npm, так что не будем замусоривать репозиторий.
Создаем репозиторий, коммитимся и заливаем все на GitHub:
$ git init $ git add . $ git commit -m 'Hello, World' $ git remote add origin git@github.com:<username>/node-demo-app.git $ git push -u origin master
2.2.3 Структура приложения
Express пока не диктует строгой структуры для файлов приложения, так что мы придумаем свою. Предлагаю такой вариант:
/node-demo-app |- /app | |- /controllers - контроллеры | |- /models - модели | |- /views - html темплейты | |- config.js - файл с настройками приложения | |- main.js - основной файл приложения |- /public - статика - картинки, клиентские скрипты, стили и т.д. |- /tests - автоматические тесты |- app.js - загрузчик приложения |- server.js - http сервер
Никто не заставляет придерживаться именно такой схемы расположения файлов, но мне она кажется удобной, так что просто запомним эту картинку и по мере продвижения по туториалу будем создавать необходимые файлы и директории.
2.3 Тестирование приложения
О том что такое TDD и зачем нужно писать тесты вы наверняка уже слышали, а если нет, то можете прочитать об этом здесь. В этом учебнике для тестирования приложения мы воспользуемся подходом, который называется BDD (behavior-driven development). В тестах мы будем описывать предполагаемое поведение приложения. Сами тесты разделим на две категории: integration тесты — они будут имитировать поведение пользователя и тестировать систему целиком, и unit тесты — для тестирования отдельных модулей приложения.
2.3.1 Автоматические тесты
В качестве фреймворков для написания тестов мы будем использовать библиотеки mocha (читается как мокка, кофе-мокка :)), should.js, и supertest. Mocha служит для организации описаний тест-кейсов, should.js предоставляет синтаксис для осуществления различных проверок, а supertest — это надстройка над простеньким http-клиентом, которая позволяет проверять результаты http-запросов. Для подключения библиотек сделаем необходимые изменения в package.json
{ "name": "node-demo-app" , "version": "0.0.1" , "scripts": { "start": "node server.js" } , "dependencies": { "express": "3.0.x" } , "devDependencies": { "mocha": "1.7.0" , "should": "1.2.1" , "supertest": "0.4.0" } }
Зависимости мы разместили в разделе «devDependencies», так как нет никакой необходимости тащить эти библиотеки на продакшн сервер. Для установки библиотек выполняем
$ npm install
Для того что бы понять как это работает, попробуем создать свой первый тест и прогнать его через наш фреймворк
$ mkdir tests $ touch tests/test.js
В test.js положим такой тест
describe('Truth', function () { it('should be true', function () { true.should.be.true }) it('should not be false', function () { true.should.not.be.false }) })
и запустим его
$ ./node_modules/.bin/mocha --require should --reporter spec tests/test.js
Вполне естественно, что такой тест пройдет, так что заменим его на что-то неработающее
describe('foo variable', function () { it('should equal bar', function () { foo.should.equal('bar') }) })
запускаем
$ ./node_modules/.bin/mocha --require should --reporter spec tests
и видим, что тесты не прошли, придется чинить код, добавляем объявление переменной
var foo = 'bar' describe('foo variable', function () { it('should equal bar', function () { foo.should.equal('bar') }) })
запускаем
$ ./node_modules/.bin/mocha --require should --reporter spec tests/test.js
и видим что код рабочий.
Основной принцип TDD состоит в том, чтобы напсать тесты до того как написан код, таким образом мы можем убедиться в том, что тесты действительно что-то тестируют, а не просто запускают код на выполнение и делают проверки в стиле true.should.be.true. То есть процесс разработки выглядит следующим образом:
- Пишем тест
- Выполняем тест и убеждаемся в том что он падает
- Пишем код
- Выполняем тест и убеждаемся в том что он проходит, если нет, возвращаемся в п.3
И так много раз.
Чтобы упростить запуск тестов добавим таск прогоняющий тесты в Makefile
$ touch Makefile
Содержимое Makefile:
REPORTER=spec TESTS=$(shell find ./tests -type f -name "*.js") test: @NODE_ENV=test ./node_modules/.bin/mocha \ --require should \ --reporter $(REPORTER) \ $(TESTS) .PHONY: test
Традиционно make использовался для сборки проекта, но его удобно использовать и в целом для автоматизации рутинных задач. Об использовании Makefile читайте здесь. Обращаю внимание на то, что отступы после названия таска должны быть сделаны табами, а не пробелами.
Теперь test-suite можно запускать коммандой:
$ make test
Попробуем потестировать http запросы. Для того чтобы сделать тестирование более удобным проведем небольшой рефакторинг кода и вынесем приложение express из файла server.js в отдельный модуль app/main.js, а также создадим файл app.js который будет этот модуль экспортировать. Сейчас это может выглядеть нецелесообразным, но такой способ организации кода нам пригодится, когда мы будем проверять покрытие кода тестами.
$ mkdir app $ touch app/main.js
app/main.js:
var express = require('express') , app = express() app.get('/', function (req, res) { res.send('Hello, World!') }) module.exports = app
$ touch app.js
app.js:
module.exports = require(__dirname + '/app/main')
server.js заменяем на
var app = require(__dirname + '/app') , port = process.env.PORT || 3000 app.listen(port, function () { console.log('Listening on port ', port) })
Для того чтобы понять как работают модули node.js, а также что означают require
и module.exports
читаем документацию
Для того, чтобы проверить корректность http запроса напишем в test.js следующий код
var request = require('supertest') , app = require(__dirname + '/../app') describe('GET /', function () { it('should contain text "Hello, Express!"', function (done) { request(app) .get('/') .expect(/Hello, Express!/, done) }) })
В этом тесте мы проверяем, что сервер отвечает нам строчкой «Hello, Express!». Так как вместо этого сервер отвечает «Hello, World!», тест упадет. Важный момент, на который нужно обратить внимание, запросы к http серверу происходят асинхронно, по-этому нам нужно будет назначить callback на завешение теста. Mocha предоставляет такую возможность с помощью функции done, которую можно опционально передать в функцию с тест-кейсом. Чтобы тест прошел, нужно заменить строчку «Hello, World!» на «Hello, Express!» в файле app/main.js и выполнить make test
.
2.3.2 Покрытие кода тестами
В принципе, этот параграф можно пропустить, так как на процесс написания тестового приложения он никак не влияет, но отчет о покрытии кода тестами будет приятным дополнением к нашему test-suite.
Чтобы выяснить насколько полно наш код покрыт тестами, потребуется еще один инструмент, он называется jscoverage. Его придется скомпилировать. Так что если у вас еще не установлен компилятор, стоит его поставить:
$ sudo apt-get install g++
После чего устанавливаем jscoverage:
$ cd /tmp $ git clone git://github.com/visionmedia/node-jscoverage.git $ cd node-jscoverage $ ./configure && make $ sudo make install
Вернемся в директорию проекта:
$ cd ~/projects/node-tutorial/node-demo-app/
Нам потребуется внести некоторые изменения в Makefile и app.js чтобы иметь возможность генерировать отчеты о покрытии.
Makefile:
REPORTER=spec TESTS=$(shell find ./tests -type f -name "*.js") test: @NODE_ENV=test ./node_modules/.bin/mocha \ --require should \ --reporter $(REPORTER) \ $(TESTS) test-cov: app-cov @APP_COV=1 $(MAKE) --quiet test REPORTER=html-cov > coverage.html app-cov: @jscoverage app app-cov .PHONY: test
app.js:
module.exports = process.env.APP_COV ? require(<strong>dirname + '/app-cov/main') : require(</strong>dirname + '/app/main')
Мы добавили таск test-cov в Makefile так что теперь для генерации отчета coverage.js достаточно будет запустить make test-cov
. Изменения в app.js связаны с тем, что для генерации отчета используется инструментированная копия приложения, которую генерирует jscoverage. То есть мы проверяем переменную окружения APP_COV
и если она установлена загружаем приложение из директории /app-cov, а если нет, то берем обычную версию из /app.
Генерируем отчет:
$ make test-cov
Должен появиться файл coverage.html, который можно открыть в браузере.
Осталось добавить в .gitignore app-cov и coverage.html:
$ echo 'app-cov' >> .gitignore $ echo 'coverage.html' >> .gitignore
С тестами мы разобрались, так что удаляем тестовый тест
$ rm tests/test.js
И коммитимся
$ git add . $ git ci -m "Added testing framework" $ git push
Исходники демонстрационного приложения можно получить тут: github.com/DavidKlassen/node-demo-app
На подходе третья глава, в ней мы напишем полноценный контроллер для страниц сайта и разберемся с тем как работает шаблонизация в express.
ссылка на оригинал статьи http://habrahabr.ru/post/158185/
Добавить комментарий