
Дізнайтесь більше про нові кар'єрні можливості в 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?