2. О себе
Владислав Безверхий, web-developer, A2 Design
Интересуюсь IT с 2005 года
Пишу всякие штуки для себя с 2009 года
Работаю в IT с 2012 года
Увлекаюсь музыкой, играювожу D&D. Люблю маму, папу, фракталы,
парадоксы и функциональное программирование.
3. Жизнь без тестов
● Нельзя с уверенностью сказать что код работает.
● Каждая новая фича - беда для разработчика
● Боишься регрессий как огня
● При удачном стечении обстоятельств может сломаться production и
разбиться сердце заказчика product owner’а
4.
5. Решение?
● соблюдение принципов разработки (например
KISS/DRY)
● Модульное тестирование (Unit tests)
● Функциональное тестирование
● соблюдение code-styles
● применение методологии разработки
6. А что с фронт-ендом?
● Зависимость от сервера
● Зависимость от браузера
● DOM дерево - не всегда твой друг
● Иногда бывает callback hell
● Можно ловкой глобальной переменной
сломать всё
7.
8. Что из этого можно решить с
помощью Unit-тестов?
● Зависимость от сервера - mock объекты
● Зависимость от браузера - можно прогонять тесты в
разных браузерах.
● Операции над DOM деревом можно тестировать
● Сallback hell - с помощью spy функций
● Глобальная переменная быстро проявит себя в
хорошо написанных тестах
9. Средства тестирования
Jasmine JS
● полная поддержка BDD
● methods chaining
● написана в первую очередь для браузера
● имеются пакеты для python и ruby
● дружит с Sinon
10. Средства тестирования
Mocha js
● Поддерживает BDD и TDD
● Написана с поддержкой браузера и Node JS
● Возможность подключать разные библиотеки для asserts
● Дружит с Sinon и Chai
15. Пишем первый тест
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);
});
});
});
16. Тестируем свой код
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;//
};
17. Тестируем свой код
describe('coordsPolynome', function() {
it('should be clear', function() {
assert.equal(MapHandler.coordsPolynome(50),MapHandler.coordsPolynome(50));
assert.equal(MapHandler.coordsPolynome(-10),MapHandler.coordsPolynome(10));
});
});
21. Chai plugins
● Для фреймворков
● Для jQuery
● Для всевозможных изменений (DOM,
promises etc.)
● Можно писать свои - есть API
22. Немного о Sinon
● Function Spies
● Stubs
● Fake Ajax
● Fake XMLHttpRequest
● Fake Server
● Faking Time
23. Sinon - simplify your testing
● Framework- agnostic (поддерживает
Jasmine, Mocha)
● Нет зависимостей
● Можно использовать как на сервере так и
в браузере
24. Sinon Spies
Проблемы:
● Отслеживание вызовов callback’ов
● Отслеживание контекста внутри callback’ов
● Отслеживание переменных переданных в callback
● Отслеживание влияния callback’а на внешний scope
25. function once(fn) {
var returnValue, called = false;
return function () {
if (!called) {
called = true;
returnValue = fn.apply(this, arguments);
}
return returnValue;
};
}
26. it("calls the original function", function () {
var callback = sinon.spy();
var proxy = once(callback);
proxy();
assert(callback.called);
});
27. 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);
});
28. 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));
});
29. Sinon - Mocks
● Тестируемая функция зависит от какого
либо внешнего API (например
фреймворка)
● Тестируема функция зависит от внешнего
объекта
30. 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();
});
31. Sinon - Stubs
Проблемы:
● Вызов функций за пределами теста
● Дублирование тестов
● Результат вызываемой функции зависит
от временивнешних условий
● Функция является замыканием
32. it("returns the return value from the original function",
function () {
var stub = sinon.stub().returns(42);
var proxy = once(stub);
assert.equals(proxy(), 42);
});
33. Sinon - Fake XHR
Проблемы:
● Время отклика замедляет тестирование
● Сервер может быть не всегда доступен
● Запрос формируется динамически
● Выполнение запроса повлечет изменение
данных на сервере
34. 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();
});
35. 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");
});
36. Sinon (Fake Server) - testing
Проблемы:
● Недоступностьотсутствие сервера
● Время отклика
● Несоответствие API сервера текущей
спецификации
● Запросы влияют на данные сервера
38. 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);
})
39. 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);
});
40. Sinon (Timers) - prepare
Проблемы:
● Может сильно замедлить прохождение
тестов
● Изменение состояния объектов в рамках
setInterval
● Очень не удобно отлаживать
41. function throttle(callback) {
var timer;
return function () {
clearTimeout(timer);
var args = [].slice.call(arguments);
timer = setTimeout(function () {
callback.apply(this, args);
}, 100);
};
}