Upload
-
View
89
Download
0
Embed Size (px)
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
Спасибо за внимание