Наконец-то решил взяться за автоматизированное тестирование js-кода, покрытие его спеками. Ну и решил изучить вопрос поглубже (с помощью Саши Косса, нашего фронт-енд лида).

В js-сообществе нет четких соглашений по поводу того, как правильно тестировать код, какие инструменты использовать. А этих инструментов немало. Поэтому я всего лишь опишу один из способов.

Во-первых, нам понадобится node.js, несколько пакетов для него (инструменты для тестирования), и ранер спек, написанный ребятами из гугла — karma runner. Весь процесс описан для Mac OSX, скорее всего, заработает и на других юниксовых операционных системах.

Создадим директорию нашего тестового проекта. Допустим, наш проект будет называться testenv.js:

mkdir -p ~/projects/testenv
cd ~/projects/testenv

Сразу же опубликуем всё это дело на гитхабе (https://github.com/s0ber/testenv):

git init
git remote add s0ber git@github.com:s0ber/testenv.git
git fetch s0ber
git merge s0ber/master
git checkout -b development
git push s0ber -u development

В настройках репозитория я сразу же делаю ветку development основной и вся работа теперь будет вестись в ней, в мастер будут мержиться "рабочие версии". Создаю проект для sublime text, добавляю файлы проекта в .gitignore. Дальше создаем файл package.json со списком пакетов node.js:

{
  "name": "testenv",
  "version": "0.1.0",
  "author": "Sergey Shishkalov <sergeyshishkalov@gmail.com>",
  "description": "Simple evironment for running javascript specs",
 
  "devDependencies": {
    "coffee-script": "",
    "karma": "",
    "mocha": "",
    "karma-mocha": "",
    "chai": "",
    "karma-chai": "",
    "sinon": "",
    "sinon-chai": "",
    "karma-sinon-chai": ""
  },
 
  "license": "MIT",
 
  "engine": {
    "node": ">= 0.10"
  },
 
  "scripts": {
    "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS"
  }
}

Здесь в devDependencies мы указываем список зависимостей, пустые значения версий будут автоматически ставить последние версии указанных пакетов. Директива "scripts" нужна для различных CI-сервисов (типа Travis), для автоматического запуска тестов, в данном случае тесты в таких сервисах будут запускаться с помощью следующей команды:

./node_modules/.bin/karma start --single-run --browsers PhantomJS

Пройдемся по пакетам:

  1. coffee-script — в представлении не нуждается, без него не представляю жизни
  2. karma — утилита для запуска спек, написанная разработчиками гугла (замена для guard, grunt, rake тасков и т.д.)
  3. mocha — мощный фреймворк для тестирования js-кода
  4. karma-mocha — адаптер mocha для karma
  5. chai — прокладка для mocha, предоставляющая красивый BDD-синтаксис для написания спек
  6. karma-chai — адаптер chai для karma
  7. sinon — стабы и моки для наших спек
  8. sinon-chai — адаптер sinon для chai
  9. karma-sinon-chai — адаптер sinon-chai для karma

Попытаемся установить все наши пакеты:

npm install

Всё отлично поставилось. Далее запускаем следующую команду:

./node_modules/karma/bin/karma init

Она запустит небольшой консольный визард со списком вопросов, необходимых для создания конфига. Вот какая конфигурация получилась у меня после легкой ручной модификации:

// Karma configuration
// Generated on Sat Oct 12 2013 13:57:36 GMT+0100 (CET)
 
module.exports = function(config) {
  config.set({
 
    // base path, that will be used to resolve files and exclude
    basePath: '',
 
 
    // frameworks to use
    frameworks: ['mocha', 'chai'],
 
 
    // list of files / patterns to load in the browser
    files: [
      'src/*.coffee',
      'spec/*_spec.coffee'
    ],
 
 
    // list of files to exclude
    exclude: [
 
    ],
 
 
    // test results reporter to use
    // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
    reporters: ['progress'],
 
 
    // web server port
    port: 9876,
 
 
    // enable / disable colors in the output (reporters and logs)
    colors: true,
 
 
    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,
 
 
    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,
 
 
    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['PhantomJS'],
 
 
    // If browser does not capture in given timeout [ms], kill it
    captureTimeout: 60000,
 
 
    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false
  });
};

В принципе, на данном этапе всё готово и мы можем непосредственно начать писать код и спеки для него. TDD это не только новомодный хипстерский хайп, но и действительно полезный подход к написанию кода, который делает код чище, красивее и надежнее. Учиться тут нужно очень долго и без постоянной практики далеко не уедешь, поэтому с этого момента я буду стараться писать код, придерживаясь принципов TDD и BDD.

Сейчас же попробуем написать наши первые спеки, предварительно запустив karma, чтобы она следила (директива autoWatch) за изменениями в наших файлах и автоматически прогоняла спеки с помощью PhantomJS, выводя результаты в консоль:

./node_modules/karma/bin/karma start

Напишем сложнейшую библиотеку, способную записывать значение по ключу, а потом по этому ключу его возвращать (сеттер и геттер). Не будем париться о здравом смысле и назовем ее TestEnv.

Первые спеки в файле spec/testenv_spec.coffee:

describe 'TestEnv', ->
 
  value = 'My awesome value'
 
  it 'returns provided value when setting by key', ->
    expect(TestEnv.set('testKey', value)).be.eq value
 
  it 'returns previously set value when getting by key', ->
    expect(TestEnv.get('testKey')).be.eq value

Здесь мы описываем наш простенький объект TestEnv с двумя методами set и get. Первая спека проверяет, что вызов TestEnv.set('testKey', value) вернет записываемое значение, а вторая проверит, что TestEnv.get('testKey') вернет это самое значение при попытке считать его по ключу. Смотрим консоль — тесты упали, две спеки красные. Очевидно, как это решить. Написать код для прохождения этих спек. Пишем его в src/testenv.coffee:

valuesHash = {}
 
@TestEnv =
  set: (key, value) ->
    valuesHash[key] = value
 
  get: (key) ->
    valuesHash[key]

Сохраняем изменения, смотрим в консоль — всё отлично, спеки зеленые. Посвящение в TDD окончено. Дальше остается штудировать доки, приучать себя к правилу "сначала спеки", и набираться опыта. По традиции дочитавшим до конца картинка.