Ознайомлення з фронтенд-тестуванням. Частина четверта. Інтеграційне тестування [*]


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

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


Ми розглянули два види тестування: юніт-тестування різних модулів і E2E-тестування всього додатка. Проте між цими двома етапами тестування відбуваються й інші. Я, як і багато інших, називаю такі тести “інтеграційними”.

 

Декілька слів про термінологію

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

Тому, якщо їх код використовує Ajax або localStorage, або IndexedDB і тому не може бути протестованим за допомогою юніт-тестів, вони обертають цей функціонал в інтерфейс і  “мокають” цей інтерфейс для юніт-тестів, а тестування реальної реалізації інтерфейсу називають “інтеграційним тестом”. З цієї точки зору “інтеграційний тест” просто тестує код, який взаємодіє з “реальним світом” поза тими юнітами, які працюють без урахування реального світу.

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

Моє емпіричне правило про те, чи слід використовувати реальні реалізації Ajax та інших операцій I/O (введення-виведення) в інтеграційних тестах, полягає в наступному: якщо Ви можете це зробити і тести все ще виконуються швидко і не поводяться дивно, то перевіряйте I/O. Якщо операція I/O складна, повільна або поводиться дивно, то використайте в інтеграційних тестах mock-об’єкти.

У нашому калькуляторі, на щастя, єдиним реальним I/O є DOM. Немає викликів Ajax та інших причин писати “моки”.

Фейковий DOM

Постає запитання: чи треба писати фейковий DOM в інтеграційних тестах? Застосуємо моє правило. Використання реального DOM зробить тести повільними? На жаль, відповідь – “так”: використання реального DOM означає використання реального браузера, що робить тести повільними і непередбачуваними.

Ми відокремимо велику частину коду від DOM або протестуємо всі разом в E2E-тестах? Обидва варіанти не оптимальні. На щастя, є третій варіант: jsdom. Цей чудовий і дивовижний пакет робить саме те, чого від нього чекаєш – реалізує DOM у NodeJS.

Він працює, є швидким, запускається в Node. Якщо Ви використовуєте цей інструмент, то можете не розглядати DOM як I/O. Це дуже важливо, адже відокремити DOM від фронтенд-коду складно, а то й неможливо. Наприклад, я не знаю, як зробити це, тоді припускаю, що jsdom був написаний саме для запуску фронтенд-тестів під Node.

Давайте подивимося, як він працює. Зазвичай код ініціалізації  є тестовим кодом, однак цього разу ми розпочнемо з тестового, але до того, як розпочати, – відступ.

Відступ

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

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

Повернемося до використання jsdom.

Використання jsdom

const React = require ('react') const e = React.createElementconst ReactDom = require ('react - dom')
const CalculatorApp = require('././lib/calculator-app')...describe ('calculator app component', function (){... it ('should work', function (){ ReactDom.render (e (CalculatorApp), document.getElementById ('container')) const displayElement = document.querySelector ('.display') expect (displayElement.textContent).to.equal ('0')

Цікавими є рядки з 10 по 14. У рядку 10 ми візуалізуємо компонент CalculatorApp, який (якщо Ви стежите за кодом у репозиторії) також відображає компоненти Display і Keypad.

Потім ми перевіряємо, що в рядках 12 і 14 елемент у DOM показує на дисплеї калькулятора початкове значення, що дорівнює 0.

І цей код, який працює під Node, використовує document! Глобальна змінна document є змінною браузера, але ось вона тут, у NodeJS. Щоб ці рядки працювали, потрібен дуже великий об’єм коду, який знаходиться в jsdom, і являє собою, по суті, повну реалізацію всього, що є у браузері, за вирахуванням самого рендерингу!

Рядок 10, який викликає ReactDom для візуалізації компонента, також використовує documentwindow), оскільки ReactDom часто використовує їх у своєму коді.

Отже, хто створює ці глобальні змінні? Тест, давайте подивимося на код:

 before (function (){ global.document = jsdom ('<!doctype html>

 

') global.window = document.defaultView }) after (function (){ delete global.window delete global.document })

У рядку 3 ми створюємо простий document, який містить лише div.

У рядку 4 ми створюємо глобальне window для об’єкта. Це треба React.

Функція cleanup видалить ці глобальні змінні, і вони не займатимуть пам’ять.

В ідеалі змінні document і window мають бути не глобальними. Інакше ми не зможемо запустити тести в паралельному режимі з іншими інтеграційними тестами, тому що всі вони переписуватимуть глобальні змінні.

На жаль, вони мають бути глобальними – React і ReactDom потребують того, щоб document і window були саме такими, оскільки Ви не можете їм їх передати.

Обробка подій

А як щодо іншої частини тесту? Давайте подивимося:

 ReactDom.render (e (CalculatorApp), document.getElementById ('container')) const displayElement = document.querySelector ('.display') expect (displayElement.textContent).to.equal ('0') const digit4Element = document.querySelector ('.digit - 4') const digit2Element = document.querySelector ('.digit - 2') const operatorMultiply = document.querySelector ('.operator - multiply') const operatorEquals = document.querySelector ('.operator - equals') digit4Element.click () digit2Element.click () operatorMultiply.click () digit2Element.click()
operatorEquals.click () expect (displayElement.textContent).to.equal ('84')

Інша частина тесту перевіряє сценарій, в якому користувач натискає “42 * 2 =” і повинен отримати ” 84″.

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

var ev = new Event ("keyup", ...);document.dispatchEvent (ev);

Вбудований метод click працює, тому ми використовуємо його.

Так просто!

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

А чому, якщо тести однакові, потрібний інтеграційний? Тому, що це навчальний проект, а не справжній. Два компоненти складають увесь додаток, тому інтеграційні та E2E-тести роблять те саме. Однак у реальному додатку E2E-тест складається із сотень модулів, тоді як інтеграційні тести мають декілька, можливо 10 модулів. Таким чином, у реальному додатку буде близько 10 E2E-тестів, але й сотні інтеграційних тестів.

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

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


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

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