Вчимося тестувати javascript код

Що я маю на увазі під практиками і парадигмами? Звичайно ж, архітектурний шаблон MVC (model view controller) і патерни організації коду. Слідуючи цим не хитрим премудростям, ти зможеш писати більш якісний код, який буде не тільки легко супроводжуватися, але мати здатність до автоматичного тестування.
Помилка більшості тестерів
Ні для кого не секрет, що найпопулярнішим способом тестування завжди була банальна перевірка на «око». Його суть проста до неподобства - написав пару тисяч рядків коду, вирішив задачу і запускаєш своє творіння. Погрався, покликавши - ніби все працює, можна заливати на бойовий сервер. Все гранично просто і при належній увазі розробника (в ідеалі окремої людини на прізвисько «тестер»), можна покластися на коректність роботи програми.
На практиці ж все відбувається дещо інакше. Окремої тестувальника, як правило, немає. Розробник сам намагається перевірити працездатність програми, виконуючи певну в технічному завданні послідовність дій. Більш просунуті кузні коду, автоматизують подібне інтеграційне тестування за допомогою речей на зразок Selenium.
Таким чином, програміст отримує можливість виявити лише найгрубіші помилки. На жаль, «тупі» і «непередбачені» призначені для користувача дії, а також хитрі ходи в бізнес логіці, в 99% випадків залишаються за кадром.
Наявність окремої людини в особі тестувальника вирішує проблему теж частково і до певного часу. Навіть якщо відкинути його саперна уважність до деталей, то якість його тестування буде прагнути до нуля із зростанням додатки. Наведу приклад з практики.
Unit тести як срібна куля
Уберегти свої нерви і підвищити гарантії працездатності окремих частин програми найкраще допомагає модульне тестування. Якщо ти ще жодного разу не стикався з цим страшним словом, то поясню коротко. Модульні тести дозволяють автоматизувати процес тестування і піддати тестам кожну функцію додатка.
Після завершення розробки нової функції (можливий варіант написання тестів і до початку розробки) девелопер пише спеціальний код для тестування свого коду. У коді для тестування потрібно зімітувати різні ситуації і повертаються значення. Наприклад, ми написали функцію для усічення прогалин (trim). Щоб протестувати її працездатність ми повинні підготувати декілька тестів, які дозволять стверджувати, що:
Ми також можемо додати тестування на інші вхідні параметри (наприклад, замінити символ пробілу табуляцією). Загалом, чим краще ми покриємо код тестами, і передбачимо можливих негативних варіантів, тим більше шансів, що в найвідповідальніший момент на голові залишиться трішки волосся.
У світі JS, тести зазвичай описуються за допомогою спеціалізованих фреймворків. У них є все необхідне для опису тестів, а також погано-бідні інструменти для систематизації звітів про хід тестування.
Тести! = Зайвий код
Розробники, які не використовують unit-тестування, люблять стверджувати, що модульне тестування вимагає написання і підтримку додаткового коду. Мовляв, терміни в реальних проектах найчастіше стислі і писати додатковий код просто немає можливості.
На рахунок стислих термінів я погоджуся, а от по частині зайвого коду готовий посперечатися. З одного боку, так, тести вимагають додаткового коду, а значить і часу на його написання. З іншого боку, цей код виконує роль подушок безпеки в автомобілі і обов'язково окупиться з ростом додатки.
Не всякий код тестується
Чому я стверджую, що замислюватися про тестування потрібно до написання основного коду? Та тому, що код, який спочатку передбачається покривати unit-тестів, пишеться в дещо іншому стилі. Не всякий код можна протестувати. Код, в якому змішується логіка і уявлення, та ще й розіпхати де неможливо нормально протестувати. Тут я завжди раджу дотримуватися декількох простих правил:
QUnit - класика жанру від творців jQuery
Для демонстрації тестів я створив найпростіший проект з наступною структорой:
Тепер подивимося на самі тести. Для здійснення перевірок працездатності нашого коду бібліотека Qunit.js пропонує нам ряд методів:
У другому лістингу я наочно показав, як застосовувати ці методи на практиці. Якщо запустити тестовий приклад в такому вигляді, то всі тести будуть успішно пройдені (див. Відповідний малюнок). Щоб побачити різницю між успішно пройденими тестами і завершується помилкою, я трохи змінив код одного тесту. У рядок з тестом за допомогою strictEqual () я свідомо додав помилковий результат (див. Відповідний малюнок).
Лістинг 1. Вміст файлу index.html
Лістинг 2. Файли тестів і функція trim ()
Головна відмінність цього прикладу від попереднього - замість обгортки test () застосовується asyncTest (), тим самим безпосередньо заявляючи, що мене цікавить тестування саме асинхронне тестування. Далі я запускаю час очікування в 500 мл. сек. За цей час функція myAsyncFunc () повинна передати дані на тестовий сервер, і якщо все ніштяк повернути true. Ось тут настає найцікавіший момент. Коли відбувається виклик asyncTest () потік виконання зупиняється і по закінченню тесту його необхідно самостійно запустити. Для управління потоком виконання в QUnit є методи start () і stop ().

Тестування асинхронних функцій за допомогою бібліотеки QUnit виконується досить просто. Останній приклад, який мені хотілося б розібрати, пов'язаний з написанням тесту, що виконує кілька асинхронних перевірок. Головне питання, яке виникає на цьому в подібних завданнях - оптимальне місце для старту потоку виконання. Офіційний док пропонує застосовувати в цих випадках щось на кшталт:
Тест для призначених для користувача дій
Лістинг 3. Логування натиснутих клавіш
Тепер спробуємо цю функцію протестувати. Насамперед, в тілі тесту нам необхідно емулювати натиснуту клавішу. Найпростіше це зробити за допомогою бібліотеки jQuery. яка дозволяє створити подію в пару рядків коду (див. лістинг 4).
Лістинг 4. Код тесту для KeyLogger
На самому початку лістингу з тестом я готую подія для емуляції натискання клавіші - «keydown». Нас буде цікавити натискання клавіші Tab (код 9). Потім, за допомогою методу trigger () я відправляю приготоване подія, після чого можна приступати до тестування. Спочатку перевіряємо загальну картину - чи була натиснута кнопка, а потім, її код.

DOM під прикриттям тестів
Phantom.JS - запускаємо тести з консолі
Писатимуть тести з допомогою бібліотеки Qunit.js зручно і просто, але рано чи пізно її тебе відвідає бажання якось автоматизувати запуск тестування і збір результатів. Наприклад, у мене для цієї справи є окрема віртуальна машина в DigitalOcean. керувати якою я можу лише за допомогою консолі.

Досить елегантно цю проблему дозволяє вирішити проект phantom.js. Це не черговий фреймворк для написання Unit-тестів. а повноцінна консольна версія движка WebKit. Якщо сказати простіше, то це додаток емулює браузер. За допомогою phantom.js реально не просто автоматизувати перевірку виконання тестів, але і вирішити безліч завдань, рано чи пізно виникають перед розробником: отримання результатів рендінга сторінок в файл (png, jpg), функції мережевого монітора (швидкість завантаження, загальна продуктивність і т. д.), емуляція дій користувача і т.д. Рекомендую не полінуватися і почитати офіційну документацію за цим проектом, обов'язково знайдеш щось цікаве для себе.

Спробуємо познайомитися з phantom.js на практиці. Щоб пропустити через phantom.js тести, підготовлені в минулому розділі і отримати результати виконання в консоль нам буде потрібно спеціальний сценарій-лоадер - run-qunit.js. Відкриваємо консоль (я працюю в Windows, тому використовую cmd) і набиваємо команду в форматі:
У моєму випадку команда запуску вийшла такою:
All tests passed
Покривати код тестами однозначно потрібно і не важливо, якого масштабу додаток ти створюєш. В черговий раз нагадую, навіть найменші програми перетворюються в неповоротких монстрів, яких необхідно підтримувати і допілівать функціонал. Добре покритий тестами коду - запорука успіху і якості. Так, ось так відразу почати писати придатний для автоматизованих тестів код непросто, але повір, всі ці муки з лишком окупляться в майбутньому. На цьому у мене на сьогодні все, удачі!
Коли на тести немає часу
При відсутності часу немає сенсу строчити тести для простих функцій (взяти той же trim () із прикладів в статті), краще зосередити на найбільш критичних ділянках коду. Дотримуватися цього ж правила слід при написанні часто змінюваного коду. Технічне завдання живого проекту частенько змінюється, і деякі функції доводиться постійно оновлювати. Такі зміни можуть спричинити за собою неприємні моменти - з новими даними змінений код працює добре, а старі органічно не перетравлює. Ось щоб не зловити тут фейл, подібні функції краще відразу покрити тестами. Запам'ятай просте правило - немає часу покрити весь код тестами, крій найважливішу його частину.

Правила хороших тестів
Проблеми phantom.js в Windows
Якщо ти зіткнувся з подібною проблемою і плануєш використовувати phantom.js на Windows, то приготуйся виконати наступний хак. Відкрий панель управління Nvidia. Знайди в дереві пункт «Параметри 3D». Правої сторони повинна з'явитися опція «Кращий графічний адаптер». За замовчування її значення встановлено в «Автоматичний вибір». Нам треба її поміняти на «Високопродуктивний процесор Nvidia» або «Інтегроване графічне обладнання». Після цього нехитрого трюку phantom.js почав вести себе слухняно.
що почитати
