Тестирование фронтенда
миф или реальность?
Чернобров Михаил
Проблемы
• Работает ли в браузере (пример .bind() и IE 8)?
• Изменили компонент – все вместе не завелось;
• Сделали новую задачу – сломали старый код;
• Устал от ctrl + r
• Добавьте свое
2
Время === ₽
• Время затраченное на ручную проверку кода
• … обновление браузера
• … написание кода
• … написание тестов
3
Решение
4
TDD
Разработка через тестирование (англ. test-
driven development, TDD) — техника разработки
программного обеспечения. Сначала пишется
тест, затем пишется код.
5
Ожидания
• Уменьшилось – Время на обновление
страницы
• Уменьшилось – Время на написание кода
• Уменьшилось – Время на ручную проверку
• Выросла – Вероятность нахождения ошибки
6
Концепции
๏ Юнит = функция -> результат
๏ Интеграционные – (Юнит + Юнит) ->
результат
๏ UI/UX – (Интеграционные + Верстка) ->
результат
7
Визуализируем
UNIT
Интеграционные
UI/UX
8
В деталях
Первая итерация
• Сборка проекта: Webpack
• Запуск тестов: Karma (+ 6 плагинов)
• Описание поведения: Jasmine
• Отчеты: Mocha
• Ожидания: Chai
• Окружение: PhantomJS
10
11
12
Определяемся с
инструментами
Внедряем
ОптимизируемПишем тесты
План
13
Результат
• Сборка проекта: Webpack
• Запуск тестов: Testem
• Описание поведения: Mocha
• Отчеты: Mocha
• Ожидания: Chai
• Окружение: PhantomJS
14
Инструменты
Webpackhttps://github.com/webpack/webpack
16
Testemhttps://github.com/testem/testem
17
Mochahttps://github.com/mochajs/mocha
18
Chaihttps://github.com/chaijs/chai
19
https://github.com/ariya/phantomjs/
20
Окружение
Режим CI
22
npm run test-ci
/app/**/__tests__/*.spec.js
testemwebpack
mochaphantom.jsreport
23
Режим TDD
24
npm run test
/app/**/__tests__/*.spec.js
testem
webpack-dev-
server
mocha
phantom.jsreport
webpack
http://localhost:8081
http://localhost:7357
25
Внедрение
Подготовка
• Установить зависмости
• Создадим папку .bin
• Положить 2 файла build-test.js и testem.js
27
package.json
{
"scripts": {
…
"build-test": "node .bin/build-test",
"test": "NODE_ENV=tdd npm run build-test",
"test-ci": "NODE_ENV=test-ci testem ci”
…
},
}
}
28
testem.json
{
"framework": "mocha",
"before_tests": "npm run build-test",
"after_tests": "rm -rf testing/",
"serve_files": [
"testing/*.js"
],
"launch_in_dev": [
"phantomjs"
],
"launch_in_ci": [
"phantomjs"
]
}
29
./bin/testem.js
function Testem() {}
Testem.prototype.start = function(options, finalizer) {
this.options = options || {};
this.config = new Config('dev', this.options);
this.app = new App(this.config, finalizer);
this.app.start();
};
Testem.prototype.restart = function() {
this.app.triggerRun('Api: restart');
};
module.exports = Testem;
30
.bin/build-test.js
glob('app/**/__tests__/*.spec.js', function(err, files) {
config.entry = {
a: dependencies,
};
files.forEach(function(file) {
config.entry[path.basename(file, '.js')]
= path.join(__dirname, '..', file);
});
// vendors
config
.plugins
.push(new webpack.optimize
.CommonsChunkPlugin('a', 'a.js'));
// check NODE_ENV
process && process.env && process.env.NODE_ENV === 'tdd'
? startServer(config)
: createCompiller(config);
});
31
.bin/build-test.js (2)
var startTestem = function(stat) {
var options = {
framework: 'mocha',
launch_in_dev: [
'phantomjs'
],
serve_files: stat.compilation.chunks.map(value => {
return `http://${host}:${port}/${value.files[0]}`;
})
};
var Instance = new Testem;
Instance.start(options, function() {});
return Instance;
};
32
.bin/build-test.js (3)
var createCompiller = function(endConfig) {
return webpack(endConfig, function() {});
};
var startServer = function(endConfig) {
var testemInstance = false;
var compiler = webpack(endConfig);
compiler.plugin('done', stat => {
!testemInstance && typeof testemInstance === 'boolean'
? testemInstance = startTestem(stat)
: testemInstance.restart();
});
return new WebpackDevServer(compiler, {
noInfo: true
}).listen(port, host);
};
33
Цель достигнута
npm run test
/app/**/__tests__/*.spec.js
testem
webpack-dev-
server
mocha
phantom.jsreport
webpack
http://localhost:8081
http://localhost:7357
34
Реализация
Формальные тесты
• Проверяют данные
• Проверяют результат работы функции
• Хранят представление о методах класса
• Решают большую часть поставленных задач
36
Прогрессивные
• Проверка производительности операций
• Углубленная проверка логики
• Проверка ассинхронных вызовов
• Тесты на быстродействие (benchmarks)
• Пользовательские действия и их результат
• Другие сценарии которые нужно автоматизировать
37
Подопытный
'use strict';
var Backbone = require('backbone');
var Model = require('./models/message');
module.exports = Backbone.View.extend({
el: document.getElementById('body'),
tagName: 'header',
className: 'example',
template: require('./templates/hello.jade'),
model: new Model,
initialize: function() {
this.render();
},
render: function() {
this.$el.append(this.template());
}
}); 38
TDD режим
npm run test
39
'use strict';
const Module = require('_modules/boilerplate');
const expect = require('chai').expect;
describe('App.modules.boilerplate', function() {
let Instance = new Module();
it('Should be an function', function() {
expect(Module).to.be.an('function');
});
it('Instance should be an object', function() {
expect(Instance).to.be.an('object');
});
it('Instance should contains few el and $el properties', function() {
expect(Instance).to.have.property('el');
expect(Instance).to.have.property('$el');
});
it('Instance should contains render() function', function() {
expect(Instance).to.have.property('render').an('function');
});
});
Формальный тест
40
Запустим
41
Ожидаемый функционал
'use strict';
// Depends
const $ = require('jquery');
const Module = require('_modules/boilerplate');
const expect = require('chai').expect;
describe('App.modules.boilerplate', () => {
let $el = $('<div>', { class: 'test-div' });
let Instance = new Module($el);
it('Click at logo should load ip data', done =>{
Instance.model.on('change:ip', () => done(), this);
Instance.$el.find('#rambler-logo').click();
});
it('Instance should contain .ip element', () => {
expect(Instance.$el.find('.ip')).to.have.length(1);
});
});
Покрываем тестами
43
Это норма
44
Напишем функционал
'use strict';
module.exports = Backbone.View.extend({
…
initialize: function() {
this.model.on('change:ip', () => this.update(), this);
this.render();
},
load: function(e) {
this.model.fetch();
e.preventDefault();
},
update: function() {
this.$el.append(
$('<div/>', { class: 'ip' })
.text(`YourIP: ${this.model.get('ip')}`)
);
}
});
45
Успех!
46
Режим CI
npm run test-ci
47
Наблюдаем статус
48
Еще раз успех!
49
Литература
50
http://bit.ly/1N8HYSu
Тестирование JS

Tdd webpack + testem + mocha + chai