Web разработка на node.js и express. Глава 2 — тестирование приложения

от автора

Не прошло и полгода как я наконец добрался до написания второй главы учебника. Первую главу я тоже немного переработал с учетом пожеланий хабражителей, так что можете снова ее просмотреть — Web-разработка на node.js и express. Изучаем node.js на практике

Глава 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. То есть процесс разработки выглядит следующим образом:

  1. Пишем тест
  2. Выполняем тест и убеждаемся в том что он падает
  3. Пишем код
  4. Выполняем тест и убеждаемся в том что он проходит, если нет, возвращаемся в п.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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *