Провівши належним чином тестування вашої програми, ви зможете зменшити кількість помилок в коді, полегшити подальшу налагодження програми, а також забезпечити добре структуру коду.

При модульному тестуванні програми на стороні клієнта виникають зовсім інші проблеми, ніж при тестуванні на стороні сервера. Коли ви маєте справу з кодом на стороні клієнта, вам постійно доводиться розділяти логіку програми і логіку DOM, а також структурувати код JavaScript в цілому.

На щастя, існує багато бібліотек для тестування на стороні клієнта, які допоможуть перевірити код, створити метрики діапазонів охоплення тестів, а також проаналізувати складність всього коду.

Навіщо потрібні тести в принципі?

Перш за все, загальне модульне тестування є методом, що забезпечує зниження кількості помилок, що гарантує те, що ваші прикладні функції будуть працювати належним чином.

Крім того тести є невід’ємною частиною таких понять, як Розробка через тестування (TDD) і Розробка, заснована на функціонуванні (BDD).

Ці дві стратегії модульного тестування допомагають розробити додаток, шляхом написання тесту до опису самої логіки програми. Розробляючи тести до написання коду, ви отримуєте можливість добре продумати структуру програми.

Причиною цього є те, що коли ви пишете тести, ви, як правило, намагаєтеся, спроектувати, як інтерфейс програми буде взаємодіяти з кодом. Завдяки цьому ви можете отримати краще розуміння конструкції додатки.

Попереднє тестування швидко вкаже вам на будь-які недоліки у вашому додатку, тому що ви пишете тестовий код, використовуючи фактичний код майбутньої програми!

TDD – це процес дослідження коду

Ви зрозумієте, що TDD допоможе вам досліджувати свій код в процесі його написання. TDD проводиться за принципом «Червоне світло, Зелене світло, Рефакторинг».

Цей принцип передбачає, що в першу чергу створюється код, достатній для запуску тесту і визначення помилки. Потім ви пишете код, який дозволяє пройти тест.

Після цього, ви думаєте над тим, що ви тільки що написали і як цей код реорганізувати. Просто і зрозуміло. BDD дещо відрізняється від цього підходу, він більше зосереджений на конкретних вимогах і специфікаціях бізнесу.

Тестування на стороні клієнта

Є багато причин того, чому ви повинні здійснювати тестування коду на стороні клієнта. Як згадувалося раніше, це допоможе зменшити кількість помилок і допоможе вам в розробці програми.

Тестування клієнтської частини має важливе значення ще й тому, що це дає вам можливість перевірити код інтерфейсу, окремо, без прив’язки до подання. Іншими словами, одним з його переваг є те, що ви отримуєте можливість протестувати код JavaScript, фактично не запускаючи програму на сервері.

Ви просто запускаєте тести і перевіряєте, чи програма працює без задіяння функціональних елементів. Часто вам навіть не потрібно мати доступ до Інтернету, поки ви не налаштуєте тести правильно.

Враховуючи, яку величезну роль грає JavaScript у сучасному веб-дизайні, важливо знати, як перевірити код і зменшити ймовірність помилки в робочому коді. Вашій босові не сподобається, якщо це станеться, і це не повинно статися!

Насправді можна почати тестування клієнтської сторони з написання тестів, пов’язаних зі звітами про помилки. Це дозволить вам отримати необхідну практику в написанні тестів, якщо до цього такого досвіду у вас не було, і вам практично доводиться починати з нуля.

Ще одна причина тестувати структуру коду на стороні клієнта полягає в тому, що створивши набір тестів один раз, ви відразу охоплюєте значну частину коду. І коли ви готові йти і додавати нові функції, ви зможете додати нову функцію, повторно запустити тести і перевірити, не втрачені деякі існуючі функції, всі вони працюють правильно.

Приступимо до роботи

Починаючи роботу з тестування на стороні клієнта, ви можете зіткнутися з низкою складнощів. Якщо ви ніколи не робили цього раніше. Однією з найважчих завдань у тестуванні на стороні клієнта є визначення того, як краще ізолювати DOM від логіки програми. Це часто означає, що вам потрібно в певному сенсі абстрагуватися від DOM.

Простіше всього домогтися цього, використовуючи фреймворки клієнтської сторони, такі як Knockout.js, Backbone.js або Angular.js. Це тільки деякі з них.

Якщо ви використовуєте одну з бібліотек зразок вище перерахованих, вам значно менше доводиться приділяти увагу тому, як ваша сторінка відображається в браузері, а також багатьом іншим елементам вашого додатка.

Зовсім необов’язково робити модульне тестування, працюючи тільки з відкритим JavaScript. Вам буде набагато легше, якщо ви будете проектувати код, абстрагувавшись від DOM.

Вибір бібліотеки для тестування

Існує велика кількість різних бібліотек для тестування, хоча три з них особливо виділяються: QUnit, Mocha і Jasmine.

Jasmine and Mocha належать до BDD школі модульного тестування, в той час як QUnit – це база модульного тестування як така.

Далі в цій статті ми будемо висвітлювати тему на прикладі QUnit, так як він дозволяє приступити до тестування на стороні клієнта дуже просто.

TDD з допомогою QUnit

Почати роботу з QUnit дуже просто. У наведеному нижче HTML-коді міститься все, що нам потрібно:

QUnit Example

Для кількох наступних прикладів давайте припустимо, що ми створюємо невеликий віджет, який ви вводите в поле поштовий індекс, і віджет з допомогою Geonames виводить нас відповідні цим індексом місто, штат і округ.

Спочатку виведене значення відображається просто як число, однак, як тільки ви ввели п’ять символів поштового індексу, витягуються дані з Geonames.

Якщо дані знайдені, то в віджеті буде виведено кілька додаткових полів: місто, штат і округ. Ми також будемо використовувати Knockout.js. Першим етапом є створення тесту помилок.

Дизайн ми продумуємо трохи раніше створення першого тесту, він, ймовірно, повинен містити не менш двох ViewModels, з цього можна почати. Спершу ми визначаємо модуль QUnit і перший тест:

module(«висновок поштового індексу»);
test(«view models повинні існувати», function() {
ok(FormViewModel, «viewModel для нашої форми повинен існувати»);
ok(AddressViewModel, «viewModel для нашого адреси повинен існувати»);
});

Якщо ви запустите цей тест, він не буде пройдено, тепер ви можете перейти до написання коду, який дозволить пройти його:


var AddressViewModel = function(options) {
};
var FormViewModel = function() {
this.address = new AddressViewModel();
};

Тепер ви побачите зелений, а не червоний колір. Такі тести можуть здатися спочатку трохи дурними, однак їх користь полягає в тому, що вони змушують вас, принаймні, замислюватися на ранніх стадіях розробки:

Не забувайте про тестування на стороні клієнта!

Наступний тест, який ми напишемо, буде перевіряти функціонал AddressViewModel. Специфікації цього віджета ми знаємо, що інші поля повинні бути спочатку приховано, поки не будуть знайдені дані, які відповідають введеному поштовим індексом:

module(«модель виведення адреси»);
test(«повинні виводитися дані міста, штату, якщо індекс був знайдений «, function() {
var address = new AddressViewModel();
ok(!address.isLocated());
address.zip(12345);
address.city(«програма»);
address.state(«bar»);
address.county(«bam»);
ok(address.isLocated());
});

Жодного фрагмента коду для цієї функції ще не було написано, але ідея полягає в тому, що isLocated буде обчислюватися через перегляд даних, і значення true буде повертатися, тільки якщо індекс, місто, штат і округ існують.

Таким чином, цей тест, звичайно, спочатку не буде пройдено. Тепер давайте складемо код, щоб пройти його:

var AddressViewModel = function(options) {
options = options || {};
this.zip = ko.вами(options.zip);
this.city = ko.вами(options.city);
this.state = ko.вами(options.state);
this.county = ko.вами(options.county);
this.isLocated = ko.computed(function() {
return this.city() && this.state() && this.county() && this.zip();
}, this);
this.initialize();
};

Тепер, якщо ви запустіть тест знову, ви побачите зелений колір!

Це концепція того, як можна використовувати TDD для складання тестів інтерфейсу. В ідеалі, після кожного проваленого тесту ви повинні були б складати простий код, який дозволить пройти цей тест, а потім повертатися до рефакторінгу коду.

Ви можете дізнатися набагато більше про практику TDD, і я пропоную вам вивчити інші джерела по цій темі. Але наведених прикладів має бути достатньо, щоб дати вам загальне уявлення про складання попередніх тестів.

Псевдо залежності з допомогою Sinon.js

Sinon.js – це бібліотека JavaScript, яка надає приховані, часткові і псевдо об’єкти JavaScript.

При складанні юніт-тестів ви повинні забезпечити тестування тільки даного «юніта» коду. Це часто означає, що ви повинні будете створити якісь псевдо або розірвані (заглушенные) залежності, щоб ізолювати досліджуваний фрагмент коду.

Sinon має гранично прості інструменти для цих цілей. Geonames API підтримує витяг даних через точку призначення JSONP, що означає, що ми можемо використовувати $.ajax.

В ідеалі ваші тести зовсім не будуть залежати від GeoNames API. Сервіс може впасти, може померти Інтернет, канал може бути просто занадто повільним, щоб виконувати запити Ajax. Sinon вирішить ці проблеми:

test(«треба тільки спробувати витягти дані, якщо введено 5 символів індексу «, function() {
var address = new AddressViewModel();
sinon.stub(jQuery, «ajax»).returns({
done: $.noop
});
address.zip(1234);
ok(!jQuery.ajax.calledOnce);
address.zip(12345);
ok(jQuery.ajax.calledOnce);
jQuery.ajax.restore();
});

У цьому тесті відбувається кілька дій. Перш за все, функція sinon.stub безпосередньо отримує доступ до jQuery.ajax, щоб відстежувати, коли він повинен викликатися, як і інші оператори.

Виходячи з того, що умова тесту свідчить: «треба тільки спробувати витягти дані, якщо введено 5 символів індексу«, ми можемо укласти, що до тих пір, поки введено тільки «1234«, Ajax не повинен викликатися жодного разу. Потім, коли введено п’ять символів, повинен бути запущений виклик Ajax.

Після цього нам потрібно відновити початковий стан jQuery.ajax, тому що ми намагаємося дотримуватися канонів модульного тестування і хочемо, щоб наші тести були автономні. Забезпечення автономності тестів важливо для того, щоб один тест не залежав від іншого, і у них не було спільних операторів. Вони можуть працювати в будь-якому порядку, і це не вплине на результати тестів.

Тепер, коли тест написаний, ми можемо запустити його, і подивитися, із-за чого він не проходить, щоб потім все ж виконати Ajax запит до Geonames:

AddressViewModel.prototype.initialize = function() {
this.zip.subscribe(this.zipChanged, this);
};
AddressViewModel.prototype.zipChanged = function(value) {
if (value.toString().length === 5) {
this.fetch(value);
}
};
AddressViewModel.prototype.fetch = function(zip) {
var baseUrl = «http://www.geonames.org/postalCodeLookupJSON»
$.ajax({
url: baseUrl,
data: {
«postalcode»: zip,
«country»: «us»
},
type: «GET»,
dataType: «JSONP»
}).done(this.fetched.bind(this));
};

Тут ми прописуємо відстеження змін даних, що вводяться поштового індексу. Всякий раз, коли вони змінюються, буде викликатися метод zipChanged. Метод zipChanged буде перевіряти, чи досягла довжина введеного індексу 5 символів.

Коли вводиться значення досягає 5 символів, буде називатися метод fetch. Ось де в справу вступає заглушка Sinon. У цей момент $.ajax насправді являє собою кришку Sinon. Тому calledOnce у тесті буде приймати значення true.

Заключний тест ми напишемо для операції повернення даних від сервісу GeoNames:

test(«повинен встановлювати інформацію про місто, грунтуючись на результатах пошуку», function() {
var address = new AddressViewModel();
address.fetched({
postalcodes: [{
adminCode1: «foo»,
adminName2: «bar»,
placeName: «bam»
}]
});
equal(address.city(), «bam»);
equal(address.state(), «foo»);
equal(address.county(), «bar»);
});

Цей тест буде перевіряти, як дані з сервера витягуються в набір AddressViewmodel. Запустіть його, і ви побачите червоне світло. Тепер потрібно зробити так, щоб тест був пройдений:

AddressViewModel.prototype.fetched = function(data) {
var cityInfo;
if (data.postalcodes && data.postalcodes.length === 1) {
cityInfo = data.postalcodes[0];
this.city(cityInfo.placeName);
this.state(cityInfo.adminCode1);
this.county(cityInfo.adminName2);
}
};

Метод просто встановлює, що в даних з сервера існує масив postalcodes, а потім встановлює відповідні властивості для viewModel.

Бачите, як це просто? Як тільки ви зробите це один раз, вам захочеться і в інших випадках вдаватися до допомоги TDD. Можливо, ви почнете з невеликих функцій, які легко перевірити. Ви будете частіше замислюватися над тим, як код взаємодіє з різними залежностями.

Тепер у вас буде набір тестів, які ви зможете запускати кожен раз, коли до вашого додатком пред’являються які-небудь нові вимоги. Навіть якщо ви пропустите щось, і код закрадеться помилка, ви зможете просто додати новий набір тестів, щоб її виправити! В кінці кінців, ви отримаєте повністю готовий до вживання код.

Діапазон охоплення тесту

Діапазон охоплення тесту дозволяє оцінити, яка частина вашого коду була перевірена при модульному тесті. Часто трапляється, що досягти 100% охоплення досить важко, та цього й не потрібно, але бажано зробити все можливе, щоб діапазон охоплення якомога більше.

Однією з новітніх і більше простих бібліотек для визначення діапазону охоплення є Blanket.js. Використовувати її з QUnit до смішного просто.

Вам всього лише потрібно взяти код прямо з домашньої сторінки Blanket.js або встановити бібліотеку разом з Bower. Потім додати код у вигляді бібліотеки в нижній частині файлу qunit.htmlпотім додати data-cover для всіх файлів, для яких ви хочете встановити діапазон охоплення тестів:

Готове. Дуже просто, і тепер у вікні QUnit у вас з’явиться індикатор охоплення тіста:

Не забувайте про тестування на стороні клієнта!

В даному прикладі ви можете бачити, що діапазон охоплення трохи не дотягує до 100%. Але так як сам код невеликий, ви легко можете збільшити зону охоплення. Ви можете розгорнути панель і чітко побачити функції, які ще не були охоплені:

Не забувайте про тестування на стороні клієнта!

В даному випадку для FormViewModel в тестах не був створений екземпляр, і тому він не охоплений тестом. Ви можете просто додати новий тест, який створює об’єкт FormViewModel, і, можливо, вставити оператор, котрий перевіряє, чи є властивість address і представляє воно собою instanceOf для AddressViewModel.

Після цього ви до свого задоволення виявите, що діапазон охоплення тесту складає 100%:

Не забувайте про тестування на стороні клієнта!

Складні тести

Так як ваші програми будуть ставати все більше і більше, було б корисно мати можливість запустити певний статичний аналіз коду JavaScript. Існує відмінний інструмент для запуску аналізу JavaScriptPlato.

Ви можете запустити plato, встановивши його через npm:

npm install -g plato

Після цього ви можете запустити plato для папки, де знаходяться коди JavaScript:

plato -r -d js/app reports

Ця команда запускає Plato для всіх файлів JavaScript, розташованих в папці «js/app» і виводить результати в reports. Plato запускає всі види метрик для вашого коду, в тому числі значення середніх рядків коду, обчислюване значення стабільності, JSHint, складність, обчислюються помилки і багато іншого:

Не забувайте про тестування на стороні клієнта!

На наведеному вище малюнку показано не так вже багато різних параметрів – просто тому, що код, з яким ми працювали, міститься все в одному файлі. Але коли ви працюєте з великим додатком, в якому багато файлів і рядків коду, ви зможете отримати інформацію, яка буде надзвичайно корисною.

Plato навіть відстежує всі запуски програми, так що ви можете наочно побачити, як з плином часу змінюється статистика.

Висновок

Тестування на стороні клієнта може здатися непростим завданням, тому на сьогоднішній день вже розроблено велику кількість корисних інструментів, які покликані зробити цей процес простіше.

Ця стаття лише поверхово описує засоби, які розроблені, щоб полегшити тестування на стороні клієнта.
Це може бути трудомістким завданням, але ви побачите, що, зрештою, переваги від застосування наборів тестів і перевірки коду переважать всі витрати.

Сподіваємося, що завдяки цій статті, ви вже найближчим часом зможете самостійно приступити до тестування коду на стороні клієнта.

Переклад статті «don’t Forget to Cover Your Client Side» був підготовлений дружною командою проекту Сайтостроение від А до Я.