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

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

Що я маю на увазі під практиками і парадигмами? Звичайно ж, архітектурний шаблон MVC (model view controller) і патерни організації коду. Слідуючи цим не хитрим премудростям, ти зможеш писати більш якісний код, який буде не тільки легко супроводжуватися, але мати здатність до автоматичного тестування.

Помилка більшості тестерів

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

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

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

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

Unit тести як срібна куля

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

Після завершення розробки нової функції (можливий варіант написання тестів і до початку розробки) девелопер пише спеціальний код для тестування свого коду. У коді для тестування потрібно зімітувати різні ситуації і повертаються значення. Наприклад, ми написали функцію для усічення прогалин (trim). Щоб протестувати її працездатність ми повинні підготувати декілька тестів, які дозволять стверджувати, що:

  • при передачі рядки «рядок» на виході ми отримаємо «рядок»;
  • при передачі терміни «рядок 9» на виході ми отримаємо «рядок 9»;
  • ...

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

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

    Тести! = Зайвий код

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

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

    Не всякий код тестується

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

  • Не потрібно писати великих функцій. Кожна функція повинна вирішувати одну проблему, а не 100500 можливих ситуацій. Наприклад, не потрібно вішати код відправки даних на сервер в функцію, що відповідає за їх підготовку;
  • Функція, що складається з більше 10 рядків коду скоріше за все погана функція;
  • Логіка і уявлення ні в якому разі не повинні бути разом;

    QUnit - класика жанру від творців jQuery

    Для демонстрації тестів я створив найпростіший проект з наступною структорой:

    Тепер подивимося на самі тести. Для здійснення перевірок працездатності нашого коду бібліотека Qunit.js пропонує нам ряд методів:

  • test () - обгортка для опису тесту;
  • ok () - твердження дозволяє перевірити істинність першого параметра. У нашому прикладі я передаю їй виклик певної нами функції trim () і порівняно зі значенням, яке я очікую отримати. Якщо умова істинно - тест пройдений;
  • equal () - метод дозволяє перевірити рівність першого і другого параметра. Відразу зверни увагу, що даний метод виконує нестрогую перевірку, тому годиться тільки для скалярних величин;
  • notEqual () - протилежний equal (). Виконується, якщо перше значення, не дорівнює другому;
  • strictEqual () -аналогічен equal () з однією лише відмінністю - він використовує сувору перевірку (тобто перевіряє ще і тип даних);
  • notStrictEqual () - метод протилежний strictEqual ();
  • deepEqual () - метод для рекурсивних тверджень, застосовується для примітивів, масивів, об'єктів;
  • notDeepEqual () - метод протилежний deepEqual ();
  • raises () - твердження для тестування функцій зворотного виклику, що генерують виняток;

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

    Лістинг 1. Вміст файлу index.html

    Лістинг 2. Файли тестів і функція trim ()

    Головна відмінність цього прикладу від попереднього - замість обгортки test () застосовується asyncTest (), тим самим безпосередньо заявляючи, що мене цікавить тестування саме асинхронне тестування. Далі я запускаю час очікування в 500 мл. сек. За цей час функція myAsyncFunc () повинна передати дані на тестовий сервер, і якщо все ніштяк повернути true. Ось тут настає найцікавіший момент. Коли відбувається виклик asyncTest () потік виконання зупиняється і по закінченню тесту його необхідно самостійно запустити. Для управління потоком виконання в QUnit є методи start () і stop ().

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

    Тестування асинхронних функцій за допомогою бібліотеки QUnit виконується досить просто. Останній приклад, який мені хотілося б розібрати, пов'язаний з написанням тесту, що виконує кілька асинхронних перевірок. Головне питання, яке виникає на цьому в подібних завданнях - оптимальне місце для старту потоку виконання. Офіційний док пропонує застосовувати в цих випадках щось на кшталт:

    Тест для призначених для користувача дій

    Лістинг 3. Логування натиснутих клавіш

    Тепер спробуємо цю функцію протестувати. Насамперед, в тілі тесту нам необхідно емулювати натиснуту клавішу. Найпростіше це зробити за допомогою бібліотеки jQuery. яка дозволяє створити подію в пару рядків коду (див. лістинг 4).

    Лістинг 4. Код тесту для KeyLogger

    На самому початку лістингу з тестом я готую подія для емуляції натискання клавіші - «keydown». Нас буде цікавити натискання клавіші Tab (код 9). Потім, за допомогою методу trigger () я відправляю приготоване подія, після чого можна приступати до тестування. Спочатку перевіряємо загальну картину - чи була натиснута кнопка, а потім, її код.

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

    DOM під прикриттям тестів

    Phantom.JS - запускаємо тести з консолі

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

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

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

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

    Спробуємо познайомитися з phantom.js на практиці. Щоб пропустити через phantom.js тести, підготовлені в минулому розділі і отримати результати виконання в консоль нам буде потрібно спеціальний сценарій-лоадер - run-qunit.js. Відкриваємо консоль (я працюю в Windows, тому використовую cmd) і набиваємо команду в форматі:

    У моєму випадку команда запуску вийшла такою:

    All tests passed

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

    Коли на тести немає часу

    При відсутності часу немає сенсу строчити тести для простих функцій (взяти той же trim () із прикладів в статті), краще зосередити на найбільш критичних ділянках коду. Дотримуватися цього ж правила слід при написанні часто змінюваного коду. Технічне завдання живого проекту частенько змінюється, і деякі функції доводиться постійно оновлювати. Такі зміни можуть спричинити за собою неприємні моменти - з новими даними змінений код працює добре, а старі органічно не перетравлює. Ось щоб не зловити тут фейл, подібні функції краще відразу покрити тестами. Запам'ятай просте правило - немає часу покрити весь код тестами, крій найважливішу його частину.

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

    Правила хороших тестів

  • Тест повинен бути максимально простим. Чим складніше тест, тим більша ймовірність допустити в ньому помилки;
  • Тести необхідно групувати на модулі, щоб потім було простіше знайти помилки і мати можливість тестувати певні частини програми;
  • Кожен тест повинен бути незалежним від інших тестів;
  • Завжди пиши окремий тест, при кожному виявленні багів;

    Проблеми phantom.js в Windows

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

    що почитати

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

    Альтернативи QUnit.js одним рядком