Комментарии 17
Не каждое приложение нуждается в таких вещах как require.JSВозможность, с одной стороны, не засорять глобальный скоуп, а с другой — иметь простой доступ к содержимому модуля для тестирования была для меня одной из причин перейти на require.js.
Проблема в том, что доступ к функции privateMethod извне уже никак не получитьЕсть мнение, что тестировать приватные методы и не нужно. Обоснование — в приватных методах содержатся детали реализации, которые, по определению, юнит-тестами покрывать не надо.
+7
в jasmine удобно пользоваться такой штукой как spyOn
например
Саму же функцию __someFn — мы протестируем отдельно.
Если же к someFn у нас не будет доступа, то возможно придется городить какие-то более хитрые данные для тестирования fnThatUsesSomeFn. Т.е приходится в той или иной степени подгонять данные для нужного ответа от __someFn, что усложняет сам тест
например
it("...", function() {
var result, fake_data = {....};
spyOn(someObj, "someFn").andReturn("fake data one");
result = someObj.fnThatUsesSomeFn(fake_data);
expect(result).toBe(.....);
spyOn(someObj, "__someFn").andReturn("fake data two");
result = someObj.fnThatUsesSomeFn(fake_data);
expect(result).toBe(.....);
});
Саму же функцию __someFn — мы протестируем отдельно.
Если же к someFn у нас не будет доступа, то возможно придется городить какие-то более хитрые данные для тестирования fnThatUsesSomeFn. Т.е приходится в той или иной степени подгонять данные для нужного ответа от __someFn, что усложняет сам тест
+2
C jasmine не знаком, из беглого чтения доки понял только, что spyOn декорирует методы для более удобного наблюдения за входными и выходными параметрами. Честно говоря, не понял, к чему относится ваш комментарий.
+1
Есть мнение, что тестировать приватные методы и не нужно. Обоснование — в приватных методах содержатся детали реализации, которые, по определению, юнит-тестами покрывать не надо.
я к тому что может их и не нужно тестировать. Но иногда все же хотелось бы иметь к ним доступ из тестового окружения. Например, для случая описанного выше.
+1
Если речь идет о юнит-тестах (а тем более, в случае BDD), то тестируется интерфейс, API. Приватные методы к интерфейсу не относятся, на то они и приватные.
+1
ну допустим
В этом примере когда мы будем тестировать someMethod1 и someMethod2 нам придется конструировать какие-то хитрые данные. только для того чтобы внутренняя функция __getSpecificShapeParam — вернула нужное нам значение. Хотя по сути, например в someMethod1 нам нужно протестировать вот этот момент
И было бы удобно получить доступ к __getSpecificShapeParam и сделать вид что функция возвращает нужное нам значение.
function ShapeManager() {
var __getSpecificShapeParam = function(shape) {
...// тут какие-то хитрые вычисления, которые зависят от фазы луны и магнитных бурь на солнце.....
}
return {
someMethod1: function(shape) {
var specific_param = __getSpecificShapeParam(shape);
if (specific_param .....) {
return true;
} else {
return false;
}
},
someMethod2: function(shape) {
var specific_param = _getSpecificShapeParam(shape);
.....
}
}
}
В этом примере когда мы будем тестировать someMethod1 и someMethod2 нам придется конструировать какие-то хитрые данные. только для того чтобы внутренняя функция __getSpecificShapeParam — вернула нужное нам значение. Хотя по сути, например в someMethod1 нам нужно протестировать вот этот момент
if (specific_param .....) {
return true;
} else {
return false;
}
И было бы удобно получить доступ к __getSpecificShapeParam и сделать вид что функция возвращает нужное нам значение.
+1
Кажется логичным ответ на вопрос, что надо ставить обработчики данных в одну кучу, обработчики событий в другую. Так я отказываюсь тестировать ту часть которая красит инпут в нужный цвет, а концентрируюсь на том, чтобы сделать возможным протестировать ту часть программы, где вычисляется в какой цвет красить инпут.Это называется разделение бизнес-логики и логики представления. И, скажем, разработчики AngularJS (как наиболее «тестоориентированного» MV*-фреймворка) уже давно позаботились о том, чтобы такие вещи писались и тестировались одним движением левой пятки.
+1
Может кто подскажет как тестировать API, например
window.location = 'http://habrahabr.ru'
, вызовы history API и тд. Также как правильно тестировать изменение DOM, тк разные браузеры по разному могут генерить html. И как тестировать jQuery объекты, когда $('.some-class') !== $('.some_class')
?0
1. В случае с API браузера, лучше написать метод, который будет выполнять необходимые вам действия:
а в тестах подменить этот метод, например с помощью spy в sinon следющим образом:
2. По поводу DOM, он у вас должен генерироваться одинаково во всех браузерах, если сгенерированный HTML разный, тесты должны падать, или вы имеете в виду что то другое?
3. jQuery объекты можно сравнивать с помощью deep equal, в chai это делается так:
Больше методов для тестирования jQuery в chai-jquery.
namespace.util = {
redirect: function(url) {
window.location = url;
}
}
а в тестах подменить этот метод, например с помощью spy в sinon следющим образом:
sinon.spy(utils, "redirect");
//...
expect(namespace.util.redirect).have.been.calledOnce;
namespace.util.redirect.restore();
2. По поводу DOM, он у вас должен генерироваться одинаково во всех браузерах, если сгенерированный HTML разный, тесты должны падать, или вы имеете в виду что то другое?
3. jQuery объекты можно сравнивать с помощью deep equal, в chai это делается так:
expect($('h1')).to.be.deep.equal($('h1'));
Больше методов для тестирования jQuery в chai-jquery.
+1
По поводу DOM я сталкивался с разной очередностью тегов (например jsfiddle.net/qM6AD/2/ сравни ff и chrome), лишними пробелами в style. Семантически получается одно и то же, но строгое равенство html не выполняется. Хотя возможно это связано с работой jQuery и phantomJS.
Вроде даже в каких-то браузерах появлялись специфические аттрибуты, но сейчас не могу найти.
Вроде даже в каких-то браузерах появлялись специфические аттрибуты, но сейчас не могу найти.
0
Если оформить валидатор объектом, то тестировать станет намного проще. Вот, смотрите:
Использование валидатора в приложении:
Как уже выше заметили, в jasmine есть удобная функция шпионов. Создадим шпиона и подставим его вместо $.ajax (для этого возможность конфига и оставили). Тогда в тесте можно будет до всего добраться, а сам он получится коротким и ясным
P.S. Можно было не выносить ajax в конфиг и написать просто
function Validator(regexp, ajax) {
this.regexp = regexp;
this.ajax = ajax;
}
Validator.prototype.validate = function(str, callback) {
if(/*validate by regexp*/) {
ajax({/*validate on server*/}).success(function(data) {
callback(data)
});
}
else {
callback(false);
}
}
Использование валидатора в приложении:
var validator = new Validator(/\d*/, $.ajax);
$('input').on('keydown', validator.validate.bind(validator));
Как уже выше заметили, в jasmine есть удобная функция шпионов. Создадим шпиона и подставим его вместо $.ajax (для этого возможность конфига и оставили). Тогда в тесте можно будет до всего добраться, а сам он получится коротким и ясным
var ajaxMock, validator;
beforeEach(function() {
ajaxMock = jasmine.createSpy('ajax').andReturn({
success: jasmine.createSpy('ajaxSuccess')
});
validator = new Validator(/\d*/, ajaxMock);
});
it('a test', function() {
var callbackSpy = jasmine.createSpy('callback')
validator.validate('123a', callbackSpy);
expect(callback).not.toHaveBeenCalled();
expect(ajaxMock).toHaveBeenCalled();
//вот как-то так можно добраться до скрытой внутри лямбды
ajaxMock.success.mostRecentCall.args[0](true);
//а потом проверить, что она отработала правильно
expect(callbackSpy).toHaveBeenCalledWith(true);
});
P.S. Можно было не выносить ajax в конфиг и написать просто
ajaxMock = spyOn($, 'ajax')
, но тогда получается та самая нечистая функция, поэтому лучше так не делать0
Ни слова не сказано про mock-объекты, а именно их использование в юнит-тестах требует писать качественный код. Умение, например, разнести в разные части кода работу с DOM, DOMEvents и логикой модуля, — имхо, золотое умение. И не покрывать тестами и так покрытый тестами jQuery, а тестировать логику работы модуля, а не его отображения.
В таком коде нам совершенно наплевать, каким образом реализуется работа с DOM при попытке показать модуль — может там просто
Впрочем, не уверен, что jasmine поддерживает полноценные мок-объекты по прототипу. Все-таки в spy есть одно ограничение — приходится писать название методов в строки. Моя EDI не поддерживает смартинпут в кавычках. Может, кто-то объяснит, как создавать моки в Jasmine без перебивания руками всех нужных методов?
var MyExample = function (options, view) {
var _view = view,
_displayed = false;
this.show = function () {
if (!_displayed) {
_displayed = true;
_view.show();
}
};
this.hide = function () {
if (_displayed) {
_displayed = false;
_view.hide();
}
};
};
В таком коде нам совершенно наплевать, каким образом реализуется работа с DOM при попытке показать модуль — может там просто
documentgetElementById("MyId").style.display = true
, а может $("#MyId").fadeIn(2000);
.Впрочем, не уверен, что jasmine поддерживает полноценные мок-объекты по прототипу. Все-таки в spy есть одно ограничение — приходится писать название методов в строки. Моя EDI не поддерживает смартинпут в кавычках. Может, кто-то объяснит, как создавать моки в Jasmine без перебивания руками всех нужных методов?
+1
Если я правильно понял вопрос, то вам поможет такой код
function isFunction(object) {
return object && getClass.call(object) == '[object Function]';
}
function mockObject(obj) {
for(var key in obj)
if(isFunction(obj[key])) {
spyOn(obj, key)
}
}
}
0
Ну круто, когда это все в коробке есть, а не писать свой велосипед для адекватных мок-объектов? Это все-таки связано с тем, что Jasmine, как и QUnit они себя позиционируют как именно движки для тестирования работы JS в браузере. Ну мой внутренний Роберт Мартин кричит, что если какая-то часть кода может по-разному работать в разных средах, то ее надо вынести в абстракцию и покрыть комментариями. И потом не придется париться, запуская тесты в разных браузерах. Но это чисто мои тараканы.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Глубинное погружение в test-driven JavaScript