Пошук уроків, статей та іншого контенту
Розберете, навіщо backend потрібні автоматизовані тести та як вони зменшують ризики під час змін у коді.
Backend отримує запити від клієнтів, обробляє дані, виконує бізнес-логіку та повертає відповіді. Помилка на будь-якому з цих етапів може призвести до:
неправильних даних у відповіді;
втрати або пошкодження даних;
помилкових розрахунків;
помилок у production;
порушення роботи вже наявних функцій.
Автоматизований тест — це програма, яка перевіряє, чи працює інша частина програми так, як очікується.
Наприклад, для функції, яка обчислює суму замовлення, можна автоматично перевірити:
чи правильно додаються товари;
чи застосовується знижка;
чи не приймаються некоректні дані.
Якщо тест проходить, ми отримуємо додаткову впевненість, що код поводиться правильно.
Розробник може вручну перевірити endpoint або функцію, але така перевірка має недоліки:
її потрібно повторювати після кожної зміни;
легко забути перевірити окремий сценарій;
результат ручної перевірки складно відтворити;
великі проєкти швидко стають занадто складними для повної ручної перевірки.
Уявімо, що backend вже вміє створювати замовлення. Через деякий час ми змінюємо розрахунок знижки. Зміна може випадково вплинути на:
загальну суму;
перевірку кількості товару;
формат відповіді;
обробку помилок.
Без тестів доводиться перевіряти всі пов’язані сценарії вручну. З тестами це можна зробити однією командою.
Тести не забороняють змінювати код. Вони допомагають швидко виявити небажані наслідки цих змін.
Типовий процес виглядає так:
Розробник змінює код.
Запускаються автоматизовані тести.
Тести перевіряють очікувану поведінку.
Якщо тест не пройшов, розробник бачить проблему до публікації змін.
Наприклад, було правило:
Для замовлення з двох товарів знижка становить 10%.
Якщо після зміни коду сума почала обчислюватися без знижки, відповідний тест повідомить про це.
Тести працюють як захисна сітка для коду. Вони особливо корисні, коли:
змінюється вже наявна логіка;
додається нова функціональність;
кілька розробників працюють над одним проєктом;
потрібно безпечно виправити помилку;
проєкт має багато взаємопов’язаних частин.
Розглянемо функцію, яка обчислює загальну вартість замовлення зі знижкою.
order.jsfunction calculateOrderTotal(items, discountPercent = 0) {
if (!Array.isArray(items) || items.length === 0) {
throw new Error('Замовлення повинно містити товари');
}
if (
typeof discountPercent !== 'number' ||
discountPercent < 0 ||
discountPercent > 100
) {
throw new Error('Некоректний відсоток знижки');
}
const subtotal = items.reduce((total, item) => {
if (
typeof item.price !== 'number' ||
typeof item.quantity !== 'number' ||
item.price < 0 ||
item.quantity <= 0
) {
throw new Error('Некоректні дані товару');
}
return total + item.price * item.quantity;
}, 0);
return subtotal * (1 - discountPercent / 100);
}
module.exports = { calculateOrderTotal };Ця функція містить кілька правил:
замовлення не може бути порожнім;
знижка повинна бути числом від 0 до 100;
ціна не може бути від’ємною;
кількість повинна бути більшою за нуль;
результат враховує знижку.
order.test.jsУ Node.js є вбудований модуль node:test, який дає змогу запускати автоматизовані тести без встановлення додаткових бібліотек.
const test = require('node:test');
const assert = require('node:assert/strict');
const { calculateOrderTotal } = require('./order');
test('обчислює загальну вартість замовлення', () => {
const items = [
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 },
];
const result = calculateOrderTotal(items);
assert.equal(result, 250);
});
test('застосовує знижку', () => {
const items = [
{ price: 100, quantity: 2 },
];
const result = calculateOrderTotal(items, 10);
assert.equal(result, 180);
});
test('відхиляє порожнє замовлення', () => {
assert.throws(
() => calculateOrderTotal([]),
{
message: 'Замовлення повинно містити товари',
},
);
});
test('відхиляє некоректну знижку', () => {
const items = [
{ price: 100, quantity: 1 },
];
assert.throws(
() => calculateOrderTotal(items, 110),
{
message: 'Некоректний відсоток знижки',
},
);
});Запустити тести можна командою:
node --test order.test.jsЯкщо всі перевірки пройшли, Node.js покаже успішний результат. Якщо одна з перевірок не пройшла, у виводі буде інформація про помилку.
Тест зазвичай має три частини:
Підготовка даних — створення вхідних значень.
Виконання коду — виклик функції або endpoint.
Перевірка результату — порівняння фактичного результату з очікуваним.
У прикладі:
const result = calculateOrderTotal(items, 10);
assert.equal(result, 180);items і 10 — вхідні дані;
calculateOrderTotal — код, який перевіряється;
assert.equal — перевірка очікуваного результату.
Тест може перевіряти не лише значення, а й помилки:
assert.throws(
() => calculateOrderTotal([]),
{
message: 'Замовлення повинно містити товари',
},
);Це важливо для backend, оскільки некоректні запити повинні оброблятися передбачувано, а не спричиняти випадкові помилки.
Добре написаний тест показує, як повинна працювати функція.
Наприклад, тест:
test('застосовує знижку', () => {
// Перевіряємо правило: знижка 10% зменшує суму на 10
});одночасно є коротким описом бізнес-правила.
Коли через кілька місяців інший розробник відкриє цей код, тести допоможуть зрозуміти:
які сценарії вважаються правильними;
які вхідні дані дозволені;
які помилки очікуються;
яку поведінку не можна випадково змінити.
Для backend важливо перевіряти не лише звичайний успішний сценарій.
Корисно додавати тести для:
коректних вхідних даних;
порожніх значень;
граничних значень;
неправильних типів даних;
від’ємних або занадто великих чисел;
відсутніх обов’язкових полів;
очікуваних помилок.
Наприклад, для знижки варто перевірити:
знижку 0%;
знижку 10%;
знижку 100%;
від’ємну знижку;
знижку понад 100%.
Такі сценарії допомагають знайти помилки, які не проявляються під час звичайної перевірки.
Наявність тестів не означає, що в програмі немає жодної помилки. Тести перевіряють лише ті сценарії, які в них описані.
Якщо написати тест тільки для одного правильного випадку, можна пропустити помилки в інших ситуаціях.
Тому важливо:
тестувати основні сценарії;
тестувати помилкові вхідні дані;
додавати тест для знайденої помилки;
запускати тести після змін у коді.
Тести зменшують ризик, але не замінюють уважне проєктування та перевірку вимог.
Припустімо, користувач повідомив, що знижка іноді обчислюється неправильно. Надійний процес виправлення такий:
Відтворити проблему в окремому тесті.
Переконатися, що тест не проходить.
Виправити код.
Запустити тест ще раз.
Запустити всі інші тести, щоб перевірити відсутність побічних змін.
Тест для виправленої помилки залишають у проєкті. Якщо така проблема з’явиться знову, автоматична перевірка виявить її одразу.
Тестування тільки правильних даних не показує, як backend поводиться з помилками.
Перевіряйте також порожні, некоректні та граничні значення.
Кожен тест повинен мати змогу виконуватися окремо. Якщо один тест змінює спільні дані, це може вплинути на результат інших тестів.
Краще перевіряти результат роботи функції, а не конкретні назви змінних або порядок внутрішніх операцій.
Тоді код можна змінювати, не переписуючи тести без потреби.
Тест має цінність лише тоді, коли його регулярно запускають. Після зміни backend потрібно перевіряти, чи всі тести продовжують проходити.
Назва тесту повинна пояснювати, яку поведінку він перевіряє:
test('відхиляє порожнє замовлення', () => {
// ...
});Так у разі помилки буде зрозуміло, яке правило порушено.
Автоматизований тест перевіряє очікувану поведінку backend-коду.
Тести допомагають виявляти помилки до потрапляння коду в production.
Вони зменшують ризик під час змін і рефакторингу.
Важливо перевіряти як успішні, так і помилкові сценарії.
Тести можуть бути коротким описом правил бізнес-логіки.
За допомогою вбудованих node:test і node:assert у Node.js можна почати тестування без додаткових бібліотек.