2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по...

Preview:

DESCRIPTION

Тестирование фронтенда - непростая задача. Mocha - один из подходов к ее решению.

Citation preview

MochaПокрой свой фронт-енд по полной

О себеВладислав Безверхий, web-developer, A2 Design

Интересуюсь IT с 2005 годаПишу всякие штуки для себя с 2009 годаРаботаю в IT с 2012 года

Увлекаюсь музыкой, играю\вожу D&D. Люблю маму, папу, фракталы, парадоксы и функциональное программирование.

Жизнь без тестов● Нельзя с уверенностью сказать что код работает.● Каждая новая фича - беда для разработчика● Боишься регрессий как огня● При удачном стечении обстоятельств может сломаться production и

разбиться сердце заказчика \ product owner’а

Решение?● соблюдение принципов разработки (например

KISS/DRY)● Модульное тестирование (Unit tests)● Функциональное тестирование● соблюдение code-styles● применение методологии разработки

А что с фронт-ендом?

● Зависимость от сервера● Зависимость от браузера● DOM дерево - не всегда твой друг● Иногда бывает callback hell● Можно ловкой глобальной переменной

сломать всё

Что из этого можно решить с помощью Unit-тестов?● Зависимость от сервера - mock объекты● Зависимость от браузера - можно прогонять тесты в

разных браузерах.● Операции над DOM деревом можно тестировать● Сallback hell - с помощью spy функций● Глобальная переменная быстро проявит себя в

хорошо написанных тестах

Средства тестирования

Jasmine JS

● полная поддержка BDD● methods chaining● написана в первую очередь для браузера● имеются пакеты для python и ruby● дружит с Sinon

Средства тестирования

Mocha js

● Поддерживает BDD и TDD● Написана с поддержкой браузера и Node JS● Возможность подключать разные библиотеки для asserts● Дружит с Sinon и Chai

Toolkit

● Mocha● Chai● Sinon

Toolkit

● Mocha - запускает тесты● Chai - предоставляет API● Sinon - mock объекты

Подключение

\> npm install mocha

\> npm install chai

\> npm install sinon

Подключаем в браузер (Mocha + Chai)<head>

<title>Express Tests</title>

<link rel="stylesheet" href="/stylesheets/tests/mocha.

css">

<script src="/javascripts/mocha.js"></script>

<script src="/javascripts/chai.js"></script>

<script src=”/javascripts/chai.js”></script>

</head>

Пишем первый тестvar should = chai.expect;

describe('Array', function() {

describe('#indexOf()', function(){

it('should return -1 when the value is not present', function(){

should([1, 3, 4].indexOf(5)).equal(-1);

should([1, 3, 4].indexOf(0)).equal(-1);

});

});

});

Тестируем свой кодvar MapHandler = function() {

var self = this;

self.squareSide = 0.11;

self.init();

};

MapHandler.prototype.coordsPolynome = function(x) {

x = Math.abs(x);

return 1.75477 * Math.pow(10, -6) * Math.pow(x, 3) -0.000365418

* Math.pow(x, 2) + 0.00845663 * x + 0.954619;//

};

Тестируем свой код

describe('coordsPolynome', function() {

it('should be clear', function() {

assert.equal(MapHandler.coordsPolynome(50),MapHandler.coordsPolynome(50));

assert.equal(MapHandler.coordsPolynome(-10),MapHandler.coordsPolynome(10));

});

});

Запускаем тесты

<script>

mocha.setup('bdd');

mocha.bail(false);

mocha.run();

</script>

</body>

А что еще дает нам chai?

Chai plugins

● Для фреймворков● Для jQuery● Для всевозможных изменений (DOM,

promises etc.)● Можно писать свои - есть API

Немного о Sinon

● Function Spies● Stubs● Fake Ajax● Fake XMLHttpRequest● Fake Server● Faking Time

Sinon - simplify your testing

● Framework- agnostic (поддерживает Jasmine, Mocha)

● Нет зависимостей● Можно использовать как на сервере так и

в браузере

Sinon SpiesПроблемы:● Отслеживание вызовов callback’ов● Отслеживание контекста внутри callback’ов● Отслеживание переменных переданных в callback● Отслеживание влияния callback’а на внешний scope

function once(fn) {

var returnValue, called = false;

return function () {

if (!called) {

called = true;

returnValue = fn.apply(this, arguments);

}

return returnValue;

};

}

it("calls the original function", function () {

var callback = sinon.spy();

var proxy = once(callback);

proxy();

assert(callback.called);

});

it("calls the original function only once", function () {

var callback = sinon.spy();

var proxy = once(callback);

proxy();

proxy();

assert(callback.calledOnce);

// ...or:

// assert.equals(callback.callCount, 1);

});

it("calls original function with right this and args", function()

{

var callback = sinon.spy();

var proxy = once(callback);

var obj = {};

proxy.call(obj, 1, 2, 3);

assert(callback.calledOn(obj));

assert(callback.calledWith(1, 2, 3));

});

Sinon - Mocks

● Тестируемая функция зависит от какого либо внешнего API (например фреймворка)

● Тестируема функция зависит от внешнего объекта

it("returns the return value from the original function",

function () {

var myAPI = { method: function () {} };

var mock = sinon.mock(myAPI);

mock.expects("method").once().returns(42);

var proxy = once(myAPI.method);

assert.equals(proxy(), 42);

mock.verify();

});

Sinon - Stubs

Проблемы:● Вызов функций за пределами теста● Дублирование тестов● Результат вызываемой функции зависит

от времени\внешних условий● Функция является замыканием

it("returns the return value from the original function",

function () {

var stub = sinon.stub().returns(42);

var proxy = once(stub);

assert.equals(proxy(), 42);

});

Sinon - Fake XHR

Проблемы:● Время отклика замедляет тестирование● Сервер может быть не всегда доступен● Запрос формируется динамически● Выполнение запроса повлечет изменение

данных на сервере

var xhr, requests;

before(function() {

xhr = sinon.useFakeXMLHttpRequest();

requests = [];

xhr.onCreate = function (req) { requests.push(req); };

});

after(function() {

// Like before we must clean up when tampering with globals.

xhr.restore();

});

it("makes a GET request for todo items", function () {

getSomething(42, sinon.spy());

assert.equal(requests.length, 1);

assert.equal(requests[0].url, "/something/42");

});

Sinon (Fake Server) - testing

Проблемы:● Недоступность\отсутствие сервера● Время отклика● Несоответствие API сервера текущей

спецификации● Запросы влияют на данные сервера

var server;

beforeEach(function () { server = sinon.fakeServer.create(); });

afterEach(function () { server.restore(); });

it("returns ok", function() {

var callback = sinon.spy();

getSomething(42, callback);

// This is part of the FakeXMLHttpRequest API

server.requests[0].respond(

200,

{ "Content-Type": "application/json" },

JSON.stringify([{ id: 1, text: "Provide examples", done:

true }])

);

assert.equal(200, server.requests[0].status);

})

it("calls callback with deserialized data", function () {

var callback = sinon.spy();

getTodos(42, callback);

// This is part of the FakeXMLHttpRequest API

server.requests[0].respond(

200,

{ "Content-Type": "application/json" },

JSON.stringify([{ id: 1, text: "Provide examples", done:

true }])

);

assert(callback.calledOnce);

});

Sinon (Timers) - prepare

Проблемы:● Может сильно замедлить прохождение

тестов● Изменение состояния объектов в рамках

setInterval● Очень не удобно отлаживать

function throttle(callback) {

var timer;

return function () {

clearTimeout(timer);

var args = [].slice.call(arguments);

timer = setTimeout(function () {

callback.apply(this, args);

}, 100);

};

}

var clock;

before(function () { clock = sinon.useFakeTimers(); });

after(function () { clock.restore(); });

it("calls callback after 100ms", function () {

var callback = sinon.spy();

var throttled = throttle(callback);

throttled();

clock.tick(99);

assert(callback.notCalled);

clock.tick(1);

assert(callback.calledOnce);

assert.equal(new Date().getTime(), 100);

}

Смотреть тесты фронтенда в консоли?

\> npm install mocha-phantomjs

Смотреть тесты фронтенда в консоли?

<script>

if (window.mochaPhantomJS) { mochaPhantomJS.run(); }

else { mocha.run(); }

</script>

Смотреть тесты фронтенда в консоли? <script>

if (window.mochaPhantomJS) { mochaPhantomJS.run(); }

else { mocha.run(); }

</script>

\> npm install mocha-phantomjs

$ mocha-phantomjs -R dot /test/file.html

$ mocha-phantomjs http://testserver.com/file.html

$ mocha-phantomjs -s localToRemoteUrlAccessEnabled=true -s webSecurityEnabled=false

http://testserver.com/file.html

$ mocha-phantomjs -p ~/bin/phantomjs /test/file.html

@rabbiabram

Спасибо за внимание