Тестирование JS. Кармический Webpack

от автора

image

Привет!

Пару месяцев назад я писал пост, о том как научить 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 файлов

image
Опыт показывает, что спеки (от англ 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

image

Первый тест

Для примера, предлагаю рассмотреть нетривиальную с точки зрения 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');   }); });

Запускаем тестирование и наблюдаем за результатом.
image

В дополнении ко всему, директория tmp/coverage/html-report/ будет содержать отчет о покрытии кода:
image

Вывод

Тесты, даже в таком формальном виде, избавят нас от обязательств перед собой.
Применив достаточную изобретательность в их декларации, мы можем уберечь себя и коллег от головной боли.

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

Пример

Смотрите по этой ссылке webpack-boilerplate

Спасибо, что прочитали.

ссылка на оригинал статьи https://habrahabr.ru/post/278503/


Комментарии

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

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