grunt – Stepan Suvorov Blog https://stepansuvorov.com/blog Release 2.0 Fri, 06 May 2016 10:48:12 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.1 Idea: Grunt/gulp/webpack config builder https://stepansuvorov.com/blog/2016/05/grunt-gulp-webpack-config-builder/ https://stepansuvorov.com/blog/2016/05/grunt-gulp-webpack-config-builder/#comments Sat, 07 May 2016 10:39:28 +0000 http://stepansuvorov.com/blog/?p=2127 Continue reading ]]> grunt-logogulp-2x1-A-_KrEvMuiH7dlwshFw5aw

I don’t know why such kind of service still have not been invented. It’s not only about grunt/gulp or webpack but also about build tools of such kind.

It could be so useful: you just feed your github repo to the service and in step by step wizard create your config file. After all you have pull request with changes(new config file) to your project.

]]>
https://stepansuvorov.com/blog/2016/05/grunt-gulp-webpack-config-builder/feed/ 4
Автоматизируем тестирование AngularJS с Protractor https://stepansuvorov.com/blog/2014/11/protractor/ https://stepansuvorov.com/blog/2014/11/protractor/#comments Tue, 18 Nov 2014 19:02:58 +0000 http://stepansuvorov.com/blog/?p=2011 Continue reading ]]>

О том как установить и запустить Protractor уже было в этом посте – Тестируем AngularJS используя Protractor. А сейчас мы сделаем фокус  на том, как мы можем интегрировать Protractor в нашу систему и подкючить к Grunt.

Подключаем Protractor к Grunt

Для подключение нам необходимо поставить специальный таск-плагин grunt-protractor-runner к grunt путем выполнения следующей команды:

$ npm install grunt-protractor-runner

После чего можем дополнить наш конфигурационный файл секцией для него:

[javascript]
protractor: {
options: {
configFile: "protractor.conf.js"
},
all: {}
}
[/javascript]

protractor.conf.js – конфигурационный файл, который уже у нас есть (создание подробно описано в предыдущем посте)

all: {} – grunt требует как минимум один “target”, поэтому, если все параметры уже указаны в конфиге протрактора, то можем просто добавить пустую сецию.

Проверим что мы все правильно настроили и команда выполняется без ошибок:

[bash]
$ grunt protractor:all
[/bash]

PhantomJS и Protractor

В случае, когда мы хотим запускать наши тесты не только локально, но и на сервере, где нет возможности использовать браузер на помощь спешит PhantomJS. Ознакомиться с PhantomJS вы можете в посте – Создаем юнит-тесты с phantomjs, а сейчас мы поговорим о том как его встроить в Protractor.

Теперь вместо вебдрайвера мы должны запустить фантом-сервер:

$ phantomjs --webdriver=4444

а наша секция grunt конфига преобразиться следующим образом:

[javascript]
protractor: {
options: {
configFile: "protractor.conf.js"
},
all: {
options: {
args:{
seleniumAddress: ‘http://localhost:4444’,
capabilities: {
browserName: ‘phantomjs’
}
}
}
}
}
[/javascript]

иногда бывает необходимо специфицировать конкретную версию/путь к phantomjs, тогда добавляем еще одну опцию – phantomjs.binary.path, а аргументы к фантому можем передавать через опцию phantomjs.cli.args; итого получим:

[javascript]
protractor: {
options: {
configFile: "protractor.conf.js"
},
all: {
options: {
args:{
seleniumAddress: ‘http://localhost:4444’,
capabilities: {
browserName: ‘phantomjs’,
‘phantomjs.binary.path’:’./node_modules/.bin/phantomjs’,
‘phantomjs.cli.args’: [‘–ignore-ssl-errors=true’, ‘–web-security=false’]
}
}
}
}
}
[/javascript]

Если мы хотим, чтобы вебдрайвер запустил фантом за нас, то просто не указываем параметр seleniumAddress.

Отладка тест-сценариев в WebStorm

Пару слов о том как можно дебажить тесты в WebStorm. Что для этого нужно сделать:

Открываем настройки Run/Debug:

Добравляем новую конфигурацию для Node.js:

Прописываем следующие ключевые настройки:

  • Working directory: корневой путь вашего проекта (например: ‘/Users/stevermeister/workspace/academy-js’)
  • JavaScript file: путь к cli.js файлу протрактора (как правило это node_modules\protractor\lib\cli.js)
  • Application parameters: путь к файлу конфиг файлу протрактора(например: protractor.conf.js)

Сохраняем. Готово:

Теперь можно проставлять бреймпоинты и дебажить.

Подключение вспомогательных файлов

Иногда возникает необходимость подключить файлы, которые не являются тест сценариями, но, в которых содержится вспомогательный функционал(например логин). Для этого необходимо подключаем файл сделать формата модуля node.js (module.exports), то есть(helpers.js):

[javascript]
function login(){
//…
}
function logout(){
//…
}
var Helper = {};
Helper.login = login;
Helper.logout = logout;
[/javascript]

и потом подключить в файле сценария:

[javascript]
var helpers = require(‘./helpers’);
helpers.login();
[/javascript]

Для тех, кто ранее не работал с таким синтаксисом, подчеркну: расширение “.js” в данном случае не пишется.

Примеры тест сценариев

Авторизация пользователя в приложении:

[javascript]
describe(‘Auth Module’, function() {
var ptor = protractor.getInstance();

it(‘should login user’, function() {
browser.get(‘/login’);
browser.waitForAngular();

element(by.model(‘username’)).sendKeys(browser.params.login.user);
element(by.model(‘password’)).sendKeys(browser.params.login.password);
element.all(by.css(‘.button–primary’)).first().click();

element(by.binding(‘user.name’)).getText().then(function(username) {
expect(username).equal(browser.params.login.username);
});
ptor.getCurrentUrl().then(function(url) {
expect(url.slice(-10)).equal(‘/dashboard’);
});
});
});
[/javascript]

Параметры типа пароля в тестах, понятное дело лучше не держать. Здесь они вынесены в отдельную секцию конфига протрактора (да, согласен, тоже не комильфо, но всеравно лучше, чем прям в тестах):

[javascript]
params: {
login: {
user: ‘stepan@mail.com’,
password: ‘XXXXXXXXX’,
username: ‘Stepan Suvorov’
}
}
[/javascript]

Покупка продукта

[javascript]
describe(‘Purchase item’, function() {
var ptor = protractor.getInstance();

beforeEach(function() {
helpers.logout();
helpers.login();
browser.sleep(3000);
});

it(‘should purchase an item’, function() {

browser.get(‘/items/552/buy’);
browser.sleep(3000);
element(by.css(‘.modal-window__block .button–primary’)).click();

ptor.ignoreSynchronization = true;

element(by.css(‘#mainSubmit’)).click();
element(by.css(‘input[type="submit"]’)).click();

ptor.ignoreSynchronization = false;
});
});
[/javascript]

хотел бы отметить очень важный момент в этом куске кода:

[javascript]
ptor.ignoreSynchronization = true;
[/javascript]

этим мы говорим протрактору, что мы покидаем приложение и переходим на страницу без AngularJS.

Поиск по каталогу

[javascript]
describe(‘Search’, function() {
it(‘should do search’, function() {

browser.get(‘/items’);
element(by.model(‘itemsSearch.query’)).sendKeys(‘iBrick’, protractor.Key.ENTER);

element.all(by.repeater(‘item in items’)).then(function(rows) {
var itemTitleElement = rows[0].element(by.className(‘item-tile__name’));
itemTitleElement.getText().then(function(itemTitle) {
expect(itemTitle).equal(‘iBrick Plus’);
})

});
});
});
[/javascript]

 

Дополнительно

]]>
https://stepansuvorov.com/blog/2014/11/protractor/feed/ 3
grunt-complexity https://stepansuvorov.com/blog/2014/06/grunt-complexity/ https://stepansuvorov.com/blog/2014/06/grunt-complexity/#respond Tue, 17 Jun 2014 20:33:45 +0000 http://stepansuvorov.com/blog/?p=1728 Continue reading ]]> В добавок к валидации кода и покрытия юнит-тестами я прикрутил интересную штуку для проекта – grunt-complexity – оценку сложности кода.

Установка – npm пакет:

$ npm install grunt-complexity

Конфигурирование в grunt-файле:

[javascript]
complexity: {
generic: {
src: [‘grunt.js’, ‘tasks/grunt-complexity.js’],
options: {
errorsOnly: false,
cyclomatic: 3,
halstead: 8,
maintainability: 100
}
}
}
[/javascript]

И запустив:

$ grunt complexity

Получим такую интересную картинку:

grunt-complexity

Теперь давайте разберем что же все таки такое complexity и из каких параметров она складывается.

Строчки кода (Line of Code or LoC)

Это может быть либо физическое количество (строчек кода в файле) либо логическое (считаются только строчки с операторами)

Количество парамтров (Number of Parameters)

Анализируется семантически(по описанию функции) и без учета использования arguments.

Цикломатическая сложность (Cyclomatic Complexity)

Количество всевозможных проходов блока кода

Цикломатическая плотность (Cyclomatic Complexity Density)

Отношение циклической сложности к логическому количеству строк кода

Критерии сложности Хальстеда (Halstead Complexity Measurement)

Основывается на количестве операторов и операндов в конкретной функции. Выделяют 3 основных критерия: Difficulty, Volume, Effort, которые могут быть получены по следующим формулам:

Difficulty = (# distinct operators / 2) * (# operands / # distinct operands)
Volume = (# operators + # operands) * log2(# distinct operators + # distinct operands)
Effort = difficulty * volume

Индекс поддержки (Maintainability Index)

Чтобы посчитать его мы должны использовать 3 уже описанные параметра:

 Maintainability =  171 - (3.42 * ln(effort)) - (0.23 * ln(cyclomatic complexity)) - (16.2 * ln(logical LOC))

Исходя из этой формулы делаем вывод что индекс может иметь значение от минус бесконечности до 171. И чем выше индекс – тем код более поддерживаемый.

С первого взгляда кажется более чем сложно, но в процессе работы, когда все делает одной командой, а еще лучше просто автоматизировано и выполняется вместе с тестами, – помогает найти слабыем места и вовремя их исправить.
Прошу прощения за очень вольный перевод терминов, если где-то неточность – поправьте пожалуйста.
]]>
https://stepansuvorov.com/blog/2014/06/grunt-complexity/feed/ 0
Сделаем gulp javascript проекту https://stepansuvorov.com/blog/2014/03/gulp/ https://stepansuvorov.com/blog/2014/03/gulp/#respond Tue, 04 Mar 2014 14:48:07 +0000 http://stepansuvorov.com/blog/?p=1417 Continue reading ]]>

Gulp.js это потоковый сборщик проектов на JavaScript, интересная альтернатива Grunt.js.

Установка

сначала глобально, чтобы можно было вызывать из командной строки:

$ sudo npm install gulp -g

потом локально, в папку проекта, чтобы его могла вызывать нода:

$ npm install gulp

проверим:

$ gulp --version
[gulp] CLI version 3.5.2
[gulp] Local version 3.5.2

установка плагинов:

$ npm install gulp-livereload gulp-imagemin gulp-uglify gulp-concat

Создание конфигурационного файла

Для gulp конфигурационным файлом является gulpfile.js.

Для начала можем создать этот файл со следующим содержимым:

var gulp = require('gulp');

gulp.task('default', function(){
  // place code for your default task here
});

и запустить, выполнив команду:

$ gulp
[gulp] Using file .../gulpfile.js
[gulp] Working directory changed to ...
[gulp] Running 'default'...
[gulp] Finished 'default' in 58 μs

Принцип вызова тасков из командной строки полностью идентичен Grunt.js.

Инициализация/подключение плагинов:

var livereload = require('gulp-livereload'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat'),
    connect = require('connect');

 

Пишем свои задания

Методы gulp, которые могут понадобиться при создании таска:

Метод Синтаксис ПримеР
gulp.task  gulp.task(name[, deps], fn)
gulp.task('do-log',
  function(){console.log('Hello!')
});
gulp.task('test', ['do-log']);
gulp.src  gulp.src(globs[, options])
gulp.src('./gulpfile.js')
gulp.dest  gulp.dest(path)
gulp.dest('main.min.css')
gulp.watch  gulp.watch(glob [, opts], tasks)
or gulp.watch(glob [, opts, cb])
var watcher = gulp.watch('js/**/*.js',
             ['uglify','reload']);
watcher.on('change', function(event){
  console.log(event.path+' -> '+event.type);
});
gulp.watch('gulpfile.js', function(changes){
console.log(changes) });
gulp.run  gulp.run(task)
 gulp.run('subtask')

Если собрать все методы в одном примере получим:

var gulp = require('gulp'),
    jshint = require('gulp-jshint');

gulp.task('jshint', function() {
  gulp.src('./gulpfile.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'));
});

gulp.task('do-log', function(){
	gulp.watch('*.js', function(changes){
		console.log(changes);
		gulp.run('jshint');
	});
});

gulp.task('start', ['do-log']);

– при изменении любого javascript файла (*.js) – выводим изменения в консоль и запускаем jshint-задание.

Вы уже вероятно обратили внимание что вся последовательность действий связанна через pipe-метод передающий по цепочке поток данных (stream). Концепция взята из node.js стримов.

С gulp можно прекрасно работать и не понимая, что происходит внутри pipe, но если вы уже перешли на следующий уровень и хотите “вклиниться в поток” либо написать свой плагин, то думаю вам может помочь разобраться этот пример:

function myPlugin(){
  var stream = through.obj(function (file, enc, callback) {
    console.log(file, enc, callback);
    this.push(file);
    return callback();
  });

  return stream;
}
var through = require('through2');
gulp.task('jshint', function() {
  gulp.src('./gulpfile.js')
  .pipe(myPlugin())
   .pipe(jshint())
   .pipe(jshint.reporter('default'));
});

Методом myPlugin мы вклиниваемся в поток выполнения при этом не нарушая его: возвращаем stream. Вместо

console.log(file, enc, callback);

может быть ваш код по изменению контента.

Запуск локального сервера

так же делается довольно просто

var connect = require('gulp-connect');
gulp.task('server', function() {
    connect
        .use(connect.static('./public'))
        .listen('3000');
});

 

 

Материалы, которые вдохновляли на пост:

]]>
https://stepansuvorov.com/blog/2014/03/gulp/feed/ 0
grunt-notify https://stepansuvorov.com/blog/2014/03/grunt-notify/ https://stepansuvorov.com/blog/2014/03/grunt-notify/#respond Sat, 01 Mar 2014 22:02:16 +0000 http://stepansuvorov.com/blog/?p=1516 Continue reading ]]>

grunt-notify – pretty useful grunt plugin that provides the adapter to notification-manager for your custom events. For example: you are doing TDD – it could inform you when tests-status switches from FAIL to SUCCESS or back. It is supported by all operational systems.

]]>
https://stepansuvorov.com/blog/2014/03/grunt-notify/feed/ 0
git hook: Не пускаем в репозиторий ошибки https://stepansuvorov.com/blog/2013/01/git-hook/ https://stepansuvorov.com/blog/2013/01/git-hook/#comments Wed, 02 Jan 2013 09:54:21 +0000 http://stepansuvorov.com/blog/?p=733 Continue reading ]]>

Настраивая систему разворачивания проекта с репозитория, мы задумались над вопросом чистоты кода репозитория, ибо тесты на самом сервере – это хорошо, но в системе контроля версий также совсем не помешает держать рабочий код, особенно в “стабильной” ветке.

Как вариант решения:  сделать git-hook, который бы проверял каждый push на репозиторий и не давал заливать “плохой” код. (Под “плохой” мы будем понимать код, который не прошел юнит-тестов либо валидации JSHint)

О самом  git hook можно почитать подробно на официальном сайте. Расскажу только некоторые детали реализации.

Все хуки лежат в директории ./git/hooks/. Там же есть уже готовые примеры реализованные на shell-скрипте(с расширением .sample).

Список возможных действий, на которые можно повесить обработчик:

  • applypatch-msg
  • post-commit
  • post-update
  • pre-commit
  • update
  • commit-msg
  • post-receive
  • pre-applypatch
  • pre-rebase

– подробное описание можно посмотреть тут.

Все перехватчики(hooks) можно разделить по месту их выполнения на клиентские и серверные. В нашем случае мы можем проверять код как на стороне клиента перед отправкой, так и на стороне сервера после получения. В первом случае решение будет менее строгим, т.к. позволит разработчику отключить или изменить его в случае чего. Для этого создаем файл pre-commit, где будет описание действия нашего перехватчика.

Теперь стоит подумать как лучше реализовать код перехватчика. Сначала была мысль написать shell-скрипт, но потом мы вспомнили о grunt, который уже прекрасно работал с проектом, и просто добавили еще одно задание(task) в него – pre-commit-test, в котором описали все необходимые проверки. Осталось только вызывать сборщик из hook-скрипта. Вот весь код:

#!/bin/sh

GRUNTJS_DIR='/path_to_project/project_dir'
GRUNT_CMD=grunt
cd $GRUNTJS_DIR
$GRUNT_CMD pre-commit-test
EXIT_CODE=$?
[ $EXIT_CODE -gt 0 ] && echo && echo validation fail! && echo
exit $EXIT_CODE

Немного комментариев:

GRUNT_CMD=grunt

пусть к команде grunt(в случае, если она не глобальная)

[ $EXIT_CODE -gt 0 ]

проверяем выдал ли что-то валидатор и в случае чего – прерываем выполнение.

]]>
https://stepansuvorov.com/blog/2013/01/git-hook/feed/ 1
Сборка javascript проектов с помощью Grunt https://stepansuvorov.com/blog/2012/10/%d1%81%d0%b1%d0%be%d1%80%d0%ba%d0%b0-javascript-%d0%bf%d1%80%d0%be%d0%b5%d0%ba%d1%82%d0%be%d0%b2-%d1%81-%d0%bf%d0%be%d0%bc%d0%be%d1%89%d1%8c%d1%8e-grunt/ https://stepansuvorov.com/blog/2012/10/%d1%81%d0%b1%d0%be%d1%80%d0%ba%d0%b0-javascript-%d0%bf%d1%80%d0%be%d0%b5%d0%ba%d1%82%d0%be%d0%b2-%d1%81-%d0%bf%d0%be%d0%bc%d0%be%d1%89%d1%8c%d1%8e-grunt/#comments Tue, 30 Oct 2012 13:53:03 +0000 http://stepansuvorov.com/blog/?p=711 Continue reading ]]>

Grunt – инструмент для сборки JavaScript проектов из командной строки. Молодой (зарелизился в январе 2012) и активно развивающийся(на данный момент для него написано уже 188 плагинов).

Благодаря тому, что grunt имеет готовый npm-пакет для node.js,  установка очень простая:

$ npm install grunt -g

Ставим grunt глобально, после чего нам доступна команда из консоли:

$ grunt --version
grunt v0.3.17

Далее необходимо инициализировать проект, для этого у нас есть команда grunt init:TEMPLATE, где TEMPLATE – один из шаблонов:

  • commonjs
  • jquery
  • node
  • gruntfile – только главный файл
  • gruntplugin

Перейдем в директорию где мы хотим создать grunt проект и выполним:

$ grunt init:jquery

после чего должно выдать серию вопросов о проекте(title, description, git-url…). После успешного прохождения этого квеста в выбранной директории появятся файлы сборщика.

Установим также PhantomJs, наличие которого предполагает grunt.

После чего можем смело запускать сборщик на выполнение:

$ grunt
Running "lint:files" (lint) task
Lint free.

Running "qunit:files" (qunit) task
Testing testproject.html....OK
>> 4 assertions passed (23ms)

Running "concat:dist" (concat) task
File "dist/testproject.js" created.

Running "min:dist" (min) task
File "dist/testproject.min.js" created.
Uncompressed size: 467 bytes.
Compressed size: 229 bytes gzipped (324 bytes minified).

Done, without errors.

– вот так вот он работает: проверил код(JSHint), запустил тесты(QUnit), соединил и сжал  файлы.

Чтобы рассмотреть инструкции по конфигурированию сборщика, проинициализируем новый проект:

$ grunt init:gruntfile

Он создаст только заготовку для главного файла(grunt.js). Откроем ее и посмотрим что внутри.

Начнем разбор с конца файла:

grunt.registerTask('default', 'lint qunit concat min');

Эта строчка говорит grunt какие задачи будут выполнены по умолчанию(т.е. запустив команду без параметров). Тут мы можем добавить свои варианты:

grunt.registerTask('prod', 'lint qunit concat min');
grunt.registerTask('test', 'lint qunit');

Возможные варианты заданий:

  • concat – конкатенация файлов
  • init – Generate project scaffolding from a predefined template.
  • lint – валидация файлов с помощью  JSHint.
  • min – сжатие файлов с UglifyJS.
  • qunit – Запуск QUnit юнит тестов на PhantomJS.
  • server – запускает статический веб сервер.
  • test – запуск юнит тестов на nodeunit.
  • watch – запуск заранее определенных заданий, для файлов из списка.

Ну а теперь последовательно по секциям конфига(по сути конфигурация для каждого задания):

meta - описание проекта, вспомогательная информация
lint/qunit/test/concat/min - список фалов, 
которые нужно отвалидировать(JSHint)/протестировать(qUnit)/протестировать(nodeUnit)/объединить/сжать(UglifyJS)
watch - список файлов и команды, которые необходимо выполнить, в случае изменения содержания файла
jshint - конфигурация JSHint
uglify - специфические настройки UglifyJS
server - пока не понятно зачем

Для каждой опции, где мы указываем список файлов существуют 2 подсекции: files и src. По умолчанию задание выполняется с командой files, но если мы хоти выполнить со списком файлов src, тогда стоит уточнить:

grunt.registerTask('build_src', 'lint:src qunit:src concat:src min:src');

Если встроенных заданий не хватает, можно писать свои и мотом подключать их с помощью методов:

grunt.loadTasks(ПУТЬ_К_ДИРЕКТОРИИ_С_ЗАДАНИЕМ);
grunt.loadNpmTasks(ИМЯ_ПЛАГИНА);

– второй вариант для дополнительных задач установленных через npm. Вот хорошая страничка, на которой перечислены самые распространенные варианты. Попробуем поставить requirejs:

npm install grunt-contrib-requirejs

и подключим в файл конфигурации:

grunt.loadNpmTasks("requirejs");

Внимание! тут есть специфика: модули установленные через npm будут подхватываться только если установлены в директорию c grunt. Подробно проблема описана вот тут.

Из основного это все.

]]>
https://stepansuvorov.com/blog/2012/10/%d1%81%d0%b1%d0%be%d1%80%d0%ba%d0%b0-javascript-%d0%bf%d1%80%d0%be%d0%b5%d0%ba%d1%82%d0%be%d0%b2-%d1%81-%d0%bf%d0%be%d0%bc%d0%be%d1%89%d1%8c%d1%8e-grunt/feed/ 3