Привет!
Пару месяцев назад я писал пост, о том как научить webpack для spa.
С того момента инструмент шагнул вперед и оброс дополнительным количеством плагинов, а так же примерами конфигураций.
В этой статье хочу поделиться опытом смешивания гремучей смеси webpack + jasmine + chai + karma.
В лучшей, по-моему мнению, книге про автоматизированное тестирование Christian Johansen — Test-Driven JavaScript Development – обозначены проблемы, с которыми разработчик сталкивается при написании кода без тестов:
– Код написан, но поведение не доступно в браузере (пример .bind() и IE 8);
– Имплементация изменена, но совокупность компонентов приводит к ошибочному или не рабочему функционалу;
– Новый код написан, нужно позаботиться о поведении со старыми интерфейсами.
Опираясь на опыт, скажу.
Программисты, избравшие путь самурая TDD (Test-driven development ), тратят много времени на покрытие кода тестами. В итоге остаются в выигрыше на этапе тестирования и отлавливания багов.
Глоссарий
– Webpack — модульный сборщик ассетов;
– Karma — test-runner для JavaScript;
– Jasmine — инструмент для определения тестов в стиле BDD;
– Chai — библиотека для проверки условий, expect, assert, should;
Установка пакетов
Для начала, приведу список пакетов, которые дополнительно устанавливаем в проект. Для этого воспользуемся npm.
#tools npm i chai mocha phantomjs-prebuilt --save-dev #karma packages #1 npm i karma karma-chai karma-coverage karma-jasmine --save-dev #karma packages #2 npm i karma-mocha karma-mocha-reporter karma-phantomjs-launcher --save-dev #karma packages #3 npm i karma-sourcemap-loader karma-webpack --save-dev
Идем дальше.
Настройка окружения
После установки дополнительных пакетов, настраиваем конфигурацию karma. Для этого в корне проекта создадим файл karma.conf.js
touch karma.conf.js
Со следующим содержанием:
// karma.conf.js var webpackConfig = require('testing.webpack.js'); module.exports=function(config) { config.set({ // конфигурация репортов о покрытии кода тестами coverageReporter: { dir:'tmp/coverage/', reporters: [ { type:'html', subdir: 'report-html' }, { type:'lcov', subdir: 'report-lcov' } ], instrumenterOptions: { istanbul: { noCompact:true } } }, // spec файлы, условимся называть по маске **_*.spec.js_** files: [ 'app/**/*.spec.js' ], frameworks: [ 'chai', 'jasmine' ], // репортеры необходимы для наглядного отображения результатов reporters: ['mocha', 'coverage'], preprocessors: { 'app/**/*.spec.js': ['webpack', 'sourcemap'] }, plugins: [ 'karma-jasmine', 'karma-mocha', 'karma-chai', 'karma-coverage', 'karma-webpack', 'karma-phantomjs-launcher', 'karma-mocha-reporter', 'karma-sourcemap-loader' ], // передаем конфигурацию webpack webpack: webpackConfig, webpackMiddleware: { noInfo:true } }); };
Конфигурирование webpack:
// testing.webpack.js 'use strict'; // Depends var path = require('path'); var webpack = require('webpack'); module.exports = function(_path) { var rootAssetPath = './app/assets'; return { cache: true, devtool: 'inline-source-map', resolve: { extensions: ['', '.js', '.jsx'], modulesDirectories: ['node_modules'] }, module: { preLoaders: [ { test: /.spec\.js$/, include: /app/, exclude: /(bower_components|node_modules)/, loader: 'babel-loader', query: { presets: ['es2015'], cacheDirectory: true, } }, { test: /\.js?$/, include: /app/, exclude: /(node_modules|__tests__)/, loader: 'babel-istanbul', query: { cacheDirectory: true, }, }, ], loaders: [ // es6 loader { include: path.join(_path, 'app'), loader: 'babel-loader', exclude: /(node_modules|__tests__)/, query: { presets: ['es2015'], cacheDirectory: true, } }, // jade templates { test: /\.jade$/, loader: 'jade-loader' }, // stylus loader { test: /\.styl$/, loader: 'style!css!stylus' }, // external files loader { test: /\.(png|ico|jpg|jpeg|gif|svg|ttf|eot|woff|woff2)$/i, loader: 'file', query: { context: rootAssetPath, name: '[path][hash].[name].[ext]' } } ], }, }; };
Мы готовы к написанию и запуску первого теста.
Определение spec файлов
Опыт показывает, что спеки (от англ spec — specification) удобнее хранить в тех же папках, что и тестируемые компоненты. Хотя, конечно же, Вы сами строите архитектуру своего приложения. В примере ниже, Вы встретите единственный для ознакомительной статьи пример теста, который расположен в директории tests модуля boilerplate.
Такое именование директорий дает позитивный отклик от новых разработчиков, желающих ознакомиться с функционалом модуля или компонента.
TL;DR открывая проект, мы видим папку со спецификациями, расположенную на первом месте за счет строковой сортировки.
Запуск
Тут ничего нового.
Для старта я использую встроенный функционал npm секции scripts.
Ровно так же как и для dev-server и "боевой" сборки функционала.
В package.json объявляем следующие команды:
"scripts": { ... "test:single": "rm -rf tmp/ && karma start karma.conf.js --single-run --browsers PhantomJS", "test:watch": "karma start karma.conf.js --browsers PhantomJS" ... }
Чтобы запустить тесты в режиме "обновляй при изменении", в корне проекта набираем команду:
npm run test:watch
Для разового запуска:
npm run test:single
Первый тест
Для примера, предлагаю рассмотреть нетривиальную с точки зрения unit тестирования задачу. Обработка результата работы Backbone.View.
Ничего страшного, если первый тест выглядит формальностью.
Рассмотрим код View:
// view.js module.exports = Backbone.View.extend({ className: 'example', tagName: 'header', template: require('./templates/hello.jade'), initialize: function($el) { this.$el = $el; this.render(); }, render: function() { this.$el.prepend(this.template()); } });
Ожидается, что при создании экземпляра View, будет вызвана функция render(). Результатом которой станет html – декларированный в шаблоне hello.jade
Пример формального теста покрывающего функционал:
// boilerplate.spec.js 'use strict'; const $ = require('jquery'); const Module = require('_modules/boilerplate'); describe('App.modules.boilerplate', function() { // подготовим переменные для использования let $el = $('<div>', { class: 'test-div' }); let Instance = new Module($el); // формальная проверка на тип возвращаемой переменной it('Should be an function', function() { expect(Module).to.be.an('function'); }); // после применения new на функции конструкторе - получим объект it('Instance should be an object', function() { expect(Instance).to.be.an('object'); }); // инстанс должен содержать el и $el свойства it('Instance should contains few el and $el properties', function() { expect(Instance).to.have.property('el'); expect(Instance).to.have.property('$el'); }); // а так же ожидаем определенной функции render() it('Instance should contains render() function', function() { expect(Instance).to.have.property('render').an('function'); }); // $el должен содержать dom element it('parent $el should contain rendered module', function() { expect($el.find('#fullpage')).to.be.an('object'); }); });
Запускаем тестирование и наблюдаем за результатом.
В дополнении ко всему, директория tmp/coverage/html-report/ будет содержать отчет о покрытии кода:
Вывод
Тесты, даже в таком формальном виде, избавят нас от обязательств перед собой.
Применив достаточную изобретательность в их декларации, мы можем уберечь себя и коллег от головной боли.
В заключении, представьте то количество времени, которое мы ежедневно тратим на каждую итерацию: "изменил – сохранил – обновил браузер – увидел результат".
Очевидное рядом. Тестирование – полезный инструмент на страже Вашего времени.
Пример
Смотрите по этой ссылке webpack-boilerplate
Спасибо, что прочитали.
ссылка на оригинал статьи https://habrahabr.ru/post/278503/
Добавить комментарий