От экспериментов с инфраструктурой
до внедрения в продакшен
Дмитрий Махнёв
Работаю в ok.ru
Делаю m.ok.ru
О себе
Автоматизация
Концентрация на новых задачах
Снижение порога входа
Улучшение стабильности
.sidebar {

...

}

.sidebar.__active {

...

}



.sidebar_item {

...

}

.sidebar_item.__selected {

...

}
∞
∞
∞
∞
/*



sidebar __active

sidebar_item (__selected)



*/
CSSG
.sidebar {

...

}

.sidebar.__active {

...

}



.sidebar_item {

...

}

.sidebar_item.__selected {

...

}
/*



sidebar __active

sidebar_item (__selected)



*/
HTML Parse
CSSG Tree
CSSG Render
HTML Parse
CSSG Tree
CSSG Render
CSS Parse …
Write in File
HTML Parse
<div class="sidebar __active">

<div class="sidebar_item">Item 1</div>

<div class="sidebar_item __selected">Item 2</div>

<div class="sidebar_item">Item 3</div>
<div class="sidebar_item">Item 4</div>
<div class="sidebar_item">Item 5</div>

</div>
Testing
describe('math test', function () {


it('sum works correct', function () {

expect(sum(1, 1)).toBe(2);

});


it('difference works correct', function () {

expect(difference(1, 1)).toBe(0);

});


});


describe('tests types', function () {



it('sync', function () {

expect(true).toBeTruthy();

});



it('async', function (done) {

setTimeout(function () {

expect(true).toBeTruthy();

done();

}, 100);

});


});
Link coming soon…
Unit Testing
Test Driven Development (TDD)
Black Box Testing
✓ Testing
describe('password security level', function () {



it('50%', function () {



expect(passwordSecurityLevel('qwerty')).toBe(50);



});



});
Testing Private Methods
Black
Box
Input Output
describe('one div with attributes', function () {



var simpleDOMResult = parse(
'<div class="block" data-foo="bar"></div>'
);


var div = simpleDOMResult.childNodes[0];



defaultDivTests(div);



});
Testing Private Methods
/**

*

* @param {ContextOfParse} contextOfParse

* @param {String} char

*/

processings[TEXT] = function (contextOfParse, char) {

addCharForBuffer(contextOfParse, char);



switch (char) {

case '<':

contextOfParse.state = TAG_START;

break;

default:

contextOfParse.textBuffer += char;

}

};
/*@defaultTesting.exports*/


processingsExport.processingText = processings[TEXT];

/*@/defaultTesting.exports*/
/**

*

* @param {String} xml

* @return {Object} simpleDOM

*/

module.exports = function (xml) {

var contextOfParse = new ContextOfParse(),

i = 0,

iMax = xml.length,

result;



for (; i < iMax; i += 1) {

processings[contextOfParse.state](contextOfParse, xml.charAt(i));

}



processingResultState(contextOfParse);



result = contextOfParse.result;

contextOfParse.destructor();



return result;

};
Tests Runners
✓ Testing
✓ Black Box Testing
Karma — Test Runner for JavaScript
Link coming soon…
Runner Workflow
var config = readConfig();



config.browsers.each(function(browser){



var result = startBrowser(browser, config.code);



console.output(processingResult(result));



});
Dependency manager
✓ Testing
✓ Black Box Testing
✓ Tests Runners
require('module');
Package Managers
Front-endBack-end & Console
require('../../../../src/default-lib');
Link coming soon…
var cycle = require('default-lib').cycle;
var webpack = require('webpack');



module.exports = {

entry: './entry.js',

output: {

path: __dirname,

filename: 'bundle.js'

}

};
webpack.config.js
var defaultLib = {};



defaultLib.getGlobal = require('./utils/getGlobal');

defaultLib.typesDetection = require('./utils/typesDetection');

defaultLib.getObjectKeys = require('./utils/getObjectKeys');

defaultLib.getObjectLength = require('./utils/getObjectLength');

defaultLib.cycleKeys = require('./utils/cycle/cycleKeys');

defaultLib.cycle = require('./utils/cycle/cycle');

defaultLib.reversiveCycle = require('./utils/cycle/reversiveCycle');

defaultLib.getObjectSafely = require('./utils/getObjectSafely');

defaultLib.onload = require('./utils/onload');



module.exports = defaultLib;
Entry Point
/* 0 */

/***/ function(module, exports, __webpack_require__) {



document.write(__webpack_require__(1));

document.body.addEventListener('click', function () {

});



/***/ },

/* 1 */

/***/ function(module, exports, __webpack_require__) {



module.exports = 'It works from content.js';



/***/ }
Part of webpack Bundle
webpack: {

resolve: {

root: [

path.join(__dirname, 'bower_components'),

path.join(__dirname, 'src')

]

},

plugins: [

new webpack.ResolverPlugin(

new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(
'bower.json',
['main']
)

)

]

}
webpack for karma.config.js
files: [

'tests/specs/simple-dom.js',

'tests/specs/modules/nodes.js',

'tests/specs/modules/parse/defaultTesting.exports.simpleDOM.parse/'

+ 'defaultTesting.exports.simpleDOM.parse.js',

'tests/specs/modules/parse/defaultTesting.exports.simpleDOM.parse/'

+ 'contextOfParse.js',

'tests/specs/modules/parse/defaultTesting.exports.simpleDOM.parse/'

+ 'microhelpers/**/*.js',

'tests/specs/modules/parse/defaultTesting.exports.simpleDOM.parse/'

+ 'builders/**/*.js',

'tests/specs/modules/parse/defaultTesting.exports.simpleDOM.parse/'

+ 'processings/**/*.js',

'tests/specs/modules/parse/parse.js',

'tests/specs/modules/selectors/parseSelector.js'

]
var path = require('path');

var webpack = require('webpack');

var bowerConfig = require('./bower');



module.exports = {



entry: path.resolve(bowerConfig.main),



output: {

path: path.resolve(bowerConfig.dist),

filename: bowerConfig.umdName + '.js',

libraryTarget: 'umd',

library: bowerConfig.umdName

},



plugins: [

new webpack.ResolverPlugin(

new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(

'bower.json',

['main']

)

),

new webpack.optimize.UglifyJsPlugin()

]

};
webpack.config.js
/*@defaultTesting.exports*/

processingsExport.processingText = processings[TEXT];

/*@/defaultTesting.exports*/
/**

*

* @param {ContextOfParse} contextOfParse

* @param {String} char

*/

processings[TEXT] = function (contextOfParse, char) {

addCharForBuffer(contextOfParse, char);



switch (char) {

case '<':

contextOfParse.state = TAG_START;

break;

default:

contextOfParse.textBuffer += char;

}

};



/*@defaultTesting.exports*/

processingsExport.processingText = processings[TEXT];

/*@/defaultTesting.exports*/
/* 0 */

/***/ function(module, exports, __webpack_require__) {



document.write(__webpack_require__(1));

document.body.addEventListener('click', function () {

});



/***/ },

/* 1 */

/***/ function(module, exports, __webpack_require__) {



module.exports = 'It works from content.js';



/***/ }
Part of webpack Bundle
webpack Workflow
var config = readConfig();

var files = [config.entry];

var bundleParts = [];



while (files.length !== 0) {



var file = loadFile(files.shift());



var ast = parseToASR(

file, 

function (linkOnFile) {

files.push(linkOnFile)

}

);



bundleParts.push(ast);



}



createBundle(bundleParts);
module: {

loaders: [

//loader removes code for testing

{

test: /^.*$/,

loader: 'regexp',

rules: [

{

'for': /regExpRule/g,

'do': ''

}

]

}

]

}
webpack.config.js loader
/*@defaultTesting.{{mode}}*/



/*@/defaultTesting.{{mode}}*/
Tasks Runner
✓ Testing
✓ Black Box Testing
✓ Tests Runners
✓ Dependency manager
Tasks Runners
Synchronously
I/Oⁿ
Streams
JS
Run Tests
Build bundle
JS
Run Tests
Build bundle Generate Doc
Commit Publish Publish × 2
gulp-run-sequence
var runSequence = require('gulp-run-sequence');



gulp.task('default', function () {

runSequence(

'build:test',

['build:dist', 'build:commit', 'build:docs'],

'build:publish'

);

});
Environment
✓ Testing
✓ Black Box Testing
✓ Tests Runners
✓ Dependency manager
✓ Tasks Runner
npm i
it('key input add 2 symbols ', function (done) {

define('',

[

'/res/js/modules/dom/domTraverse.js',

'/res/js/modules/inputWithCounter.js'

],

function (traverse, inputWithCounter) {

var initedInput = initInputWithCounterFor(

inputWithCounter,

tmpl2CountersAndWrapper

);

var counterTextNode = findCounter(initedInput, 0, traverse.getTextNode);

var counter2TextNode = findCounter(initedInput, 1, traverse.getTextNode);

var instance = initedInput.instance;

var input = instance.input;

var startValueLength = instance.valueLength;

var valueLength;



effroi.mouse.focus(input);

effroi.keyboard.hit('1', '1');



valueLength = instance.valueLength;

expect(valueLength - startValueLength).toBe(2);

expect(valueLength).toBe(+counterTextNode.nodeValue);

expect(valueLength).toBe(+counter2TextNode.nodeValue);



done();

}

);

});
inputWithCounter Test From m.ok.ru
var keyboard = require('effroi').keyboard;



keyboard.focus(element);



keyboard.hit('a'); 

keyboard.hit('b','c','d');



keyboard.combine(keyboard.CTRL, 'v');
describe('angularjs homepage todo list', function () {

it('should add a todo', function () {

browser.get('http://www.angularjs.org');



element(by.model('todoText')).sendKeys('write a protractor test');

element(by.css('[value="add"]')).click();



var todoList = element.all(by.repeater('todo in todos'));

expect(todoList.count()).toEqual(3);

expect(todoList.get(2).getText()).toEqual('write a protractor test');

});

});
wallaby.js
Ссылки
http://bit.ly/fdc_dm

«От экспериментов с инфраструктурой до внедрения в продакшен»​