Ознайомлення з фронтенд-тестуванням. Частина друга. Юніт-тестування


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

Розповідає Гіл Тайяр, автор блогу на Hackernoon


Як ми вирішили в першій частині, юніт-тест – це код, який тестує юніти (частини) коду: функції, модулі або класи. Більшість вважає, що основною масою тестів мають бути юніт-тести, але не я. Протягом усієї серії статей я стверджуватиму, що важливий не спосіб тестування, а їх кількість. Тестів повинно бути достатньо для того, щоб бути впевненим у якості продукту, що надається користувачеві.

Юніт-тести – це найпростіші тести для написання і найлегші для розуміння. Суть їх полягає в тому, щоб подати щось на вхід юніта і перевірити результат на виході (наприклад, на вхід Ви подаєте параметри функції, а на виході набуваєте значення).

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

Крім того, Ви повинні прагнути писати код так, щоб це дозволяло тестувати юніти ізольовано.

 

Юніти в додатку Calculator

Причина, через яку я не використовую JSX, полягає в тому, що я не хотів заглиблюватися в трансляцію коду. Всі сучасні браузери повністю сумісні з ES6, то чому б мені не запустити код без трансляції? Так, я знаю, що мій код не запуститься в IE, але це демо-код, так що все гаразд. У реальному проекті я б так не зробив.

Повинен же якийсь код визначати, що відбувається при натисненні на цифру (1, 5) або оператор (+, =)? Як це прийнято сьогодні, я розділив мої компоненти на презентаційні (keypad і display) і компоненти-контейнери – calculator - app. Це єдиний компонент у цьому додатку, який має стан (state), і саме цей компонент визначає, що повинне відображатися на екрані при натисненні на кнопку калькулятора.

Модуль-калькулятор

Даний компонент відповідає лише за логіку відображення, а що з обчисленнями? Цим займається окремий модуль, calculator, який не має React-залежностей. Цей модуль ідеальний для юніт-тестування! Код ідеально підходить для юніт-тестування, якщо він не містить I/O – і UI-залежностей. Ви повинні намагатися уникати таких залежностей в логіці своїх додатків.

Що означає I/O (введення / виведення) у веб-додатках? Там же немає файлів, баз даних і такого іншого? Так, цього немає, але є AJAX-виклики, localStorage і доступ до DOM. Я вважаю, що все дотичне до API браузера, – це I/O.

Як я відокремив логіку калькулятора від компонента React? У випадку з калькулятором це досить легко. Я виділив її в модуль calculator.

Модуль дуже простий – він приймає стан калькулятора (об’єкт) і символ (цифру або оператор) і повертає новий стан калькулятора. Якщо Ви колись використовували Redux, то побачите, що це схоже на шаблон редьюсера Redux. Але якщо кожен стан калькулятора залежить від попереднього, як отримати найперше? Просто – модуль також експортує initialState, яке Ви використовуєте для ініціалізації калькулятора. Стан калькулятора не є невідомим – він включає поле з ім’ям display, яке і треба показати додатку калькулятора для цього стану.

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

module.exports.initialState ={ display: '0', initial: true }module.exports.nextState =  (calculatorState, character)  => { if (isDigit (character)) { return addDigit (calculatorState, character) } else if (isOperator (character)) { return addOperator (calculatorState, character) } else if (isEqualSign (character)) { return compute (calculatorState) } else { return calculatorState }}//....

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

Саме це ми і робимо в test-calculator. Тут повністю протестована ця дуже нетривіальна логіка.

Для тестування створено безліч фреймворків. Найпопулярнішим нині є Mocha, і ми використовуватимемо саме його. Проте Ви вільні використати Jest, Jasmine, Tape або будь-який інший фреймворк, який уподобали.

Тестуємо юніт за допомогою Mocha

Усі тестувальні фреймворки схожі – Ви пишете тестовий код у функціях, що називаються “тестовими”, і фреймворк їх запускає. Конкретний код, який запускає їх, зазвичай називається “runner”.

“Runner” у Mocha – це скрипт під назвою mocha. Якщо Ви подивитеся на package.json у тестовому скрипті, ви побачите його там:

"scripts": {... "test": "mocha 'test/**/test-*.js' && eslint test lib",...},

Це запустить усі тести в тестовій теці, які розпочинаються з префікса test -. Якщо Ви клонуєте цей репозиторій і викличете npm install для нього, то зможете запустити npm test і протестувати юніт самостійно.

При запуску буде побачите щось на кшталт:

Очевидно, якщо тест не буде пройдений, він буде позначений червоним, і Ви його негайно виправите. Давайте подивимося на код:

const {describe, it} = require ('mocha') const {expect} = require ('chai') const calculator = require('././lib/calculator') describe ('calculator', function (){ const stream = (characters, calculatorState = calculator.initialState) => !characters ? calculatorState: stream (characters.slice (1), calculator.nextState (calculatorState, characters[0])) it ('should show initial display correctly', () => { expect (calculator.initialState.display).to.equal ('0')}) it ('should replace 0 in initialState', () => { expect (stream ('4').display).to.equal ('4')
})//...

Насамперед ми імпортуємо mocha і бібліотеку для перевірок (assert ‘ов) expect. Ми імпортували функції, які нам потрібні: describe, it і except.

Потім ми імпортуємо модуль, який тестуємо, – calculator.

Потім йдуть тести, описані з використанням функції it, наприклад:

it ('should show initial display correctly', () => { expect (calculator.initialState.display).to.equal ('0')})

Ця функція приймає рядок, що описує тест, і функцію, яка є самим тестом. Однак it тести не можуть бути “голими” – вони повинні знаходитися в тестових групах, які визначаються за допомогою функції describe.

А що знаходиться в тестовій функції? Все, чого ми хочемо. В даному випадку ми перевіряємо, що вихідний стан display дорівнює 0. Як ми це робимо? Ми справді могли б зробити щось подібне до цього:

if (calculator.initialState.display !== '0') throw 'failed'

Це спрацювало б дивно! Тест у Mocha не спрацьовує, якщо він генерує виключення. Це так просто. Проте expect робить його набагато кращим, адже в ній є безліч фічів, що полегшують тестування даних, – наприклад, перевірка того, що масив або об’єкт дорівнюють певному значенню.

У цьому і полягає суть юніт-тестування – запуск функції або набору функцій (чи створення екземпляра об’єкта і виклик деяких його методів, якщо йдеться про ООП) і порівняння фактичного результату з очікуваним.

Як писати добре тестовані юніти?

Складною частиною юніт-тестування є не написання тестів, а максимально можливе розбиття коду. За допомогою юніт-тестів може бути протестований той код, у якого немає I/O-залежностей і мало залежностей від інших модулів. І це важко, тому що ми маємо схильність компонувати логіку з UI-кодом і I/O-кодом. Але це можливо, і для цього є багато способів. Наприклад, якщо код перевіряє поля або групу полів, треба об’єднати всі перевірочні функції в модуль і тестувати його.

Стоп, код працює під NodeJS?!

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

Це можливо, тому що наш код ізоморфний. Це означає, що він працює і у браузері, і під NodeJS. Як же так вийшло? Якщо Ви пишете свій код, не використовуючи I/O, це означає, що він не робить нічого специфічного для конкретного браузера, отже, немає причини, через яку він би не запускався під NodeJS. Особливо якщо він використовує require, оскільки require нативно розпізнається як NodeJS, так і збирачем типу  Webpack. І якщо Ви подивитеся на package.json, то побачите, що ми використовуємо Webpack саме для того, щоб зв’язати код, який використовує require:

"scripts": { "build": "webpack && cp public/* dist", ...}

Отже, наш код використовує require для імпортування React та інших модулів, і завдяки магії NodeJS і Webpack ми можемо використати цю модульну систему як в NodeJS, так і в браузері – NodeJS розпізнає require нативний, а Webpack використовує require, щоб об’єднати всі модулі в один великий JS-файл.

Виконання юніт-тестів у браузері

До речі, ми могли б використати інший тестовий фреймворк, Karma, для запуску нашого Mocha-коду у браузері. Проте я вважаю, що якщо юніт-тести можуть виконуватися під Node, то саме так і варто робити. І якщо Ви не транслюєте код, то тести виконуються дуже швидко.

Однак не запускати тести у браузері не можна, оскільки ми не знаємо, чи працює наш код у браузері. Можливі відмінності в поведінці JS-коду у браузері і в NodeJS. І тут на допомогу приходять E2E-тести, про які ми поговоримо в наступній частині.

Переклад статті: “Testing Your Frontend Code: Part II (Unit Testing) “

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *