nmk

Лекція №23 (2 години). Тестування веб-додатків (Jest/Cypress).

План лекції

  1. Навіщо тестувати код? Піраміда тестування.
  2. Unit (Модульне) тестування: ізольована перевірка функцій.
  3. Бібліотека Jest: синтаксис та базові твердження (Assertions).
  4. Тестування React-компонентів: React Testing Library.
  5. Integration (Інтеграційне) тестування: взаємодія кількох модулів.
  6. End-to-End (Наскрізне) тестування: симуляція поведінки користувача.
  7. Знайомство з Cypress: написання та запуск E2E тестів у браузері.
  8. TDD (Test-Driven Development) — розробка через тестування.

Перелік умовних скорочень

Вступ

Уявіть, що ви написали складний інтернет-магазин на React. Ви додали нову фічу — можливість “застосувати промокод”. Ви перевірили кошик, знижка працює. Ви завантажуєте оновлення на сервер. Через годину вам дзвонить розлючений замовник: “Кнопка ‘Реєстрація’ перестала працювати! Ніхто не може купити товар!”. Виявляється, ваша нова логіка промокодів випадково зламала глобальний стан userStore, який використовувався при реєстрації.

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

Мета цієї лекції — розібратися з Пірамідою тестування та навчитися писати швидкі Unit-тести за допомогою Jest та масштабні E2E-тести за допомогою Cypress.

1. Піраміда тестування

В індустрії QA прийнято ділити всі автоматизовані тести на 3 рівні (за формою піраміди — від основи до вершини):

  1. Unit Tests (Модульні тести): Основа піраміди (70% усіх тестів). Тестують найменші частинки коду — одну окрему функцію або один React-компонент в абсолютній ізоляції від бази даних чи інтернету. Вони дуже швидкі і виконуються за мілісекунди.
  2. Integration Tests (Інтеграційні тести): Середина піраміди (20%). Перевіряють, як два або більше модулів працюють разом. Наприклад, як React-компонент “робить запит” до Redux-сховища і чи правильно воно оновлюється.
  3. E2E Tests (Наскрізні тести): Вершина піраміди (10%). Справжній робот (наприклад, Cypress) відкриває реальний браузер (Chrome), клікає по реальних кнопках, друкує текст у поля і перевіряє, чи додається товар у кошик. Вони найповільніші (можуть йти хвилинами) і найскладніші в написанні, але найточніше імітують живу людину.

2. Unit-тестування: Бібліотека Jest

Jest — це найпопулярніший тестовий фреймворк у світі JavaScript (створений Facebook). Він підходить для перевірки будь-якого JS коду.

Уявіть, що у нас є простий файл math.js з однією функцією:

// файл math.js
export function sum(a, b) {
  return a + b;
}

Щоб протестувати його, ми створюємо поруч файл math.test.js:

// файл math.test.js
import { sum } from "./math";

// Блок describe групує тести за темою (необов'язково)
describe("Тестування математичних операцій", () => {
  // Блок test (або it) - це сам тест. Перший аргумент - що ми чекаємо
  test("Функція sum має правильно додавати 1 + 2", () => {
    // 1. Arrange (Підготовка)
    const a = 1;
    const b = 2;

    // 2. Act (Дія)
    const result = sum(a, b);

    // 3. Assert (Перевірка/Твердження)
    // Ми кажемо: "Я ОЧІКУЮ (expect), що result БУДЕ ДОРІВНЮВАТИ (toBe) 3"
    expect(result).toBe(3);
  });
});

Якщо ви зумисне зміните функцію sum (наприклад, return a * b), коли ви запустите команду npm test, Jest покаже червоний екран: “Expected: 3, Received: 2”. Тест врятував ваш код від релізу з помилкою!

Базові твердження (Matchers) у Jest:

3. Тестування React-компонентів: React Testing Library (RTL)

Тестувати звичайні функції (sum, capitalizeFirstLetter) дуже легко. Але React-компонент малює складний DOM. Для цього використовується бібліотека React Testing Library (зазвичай іде в комплекті з Jest). Вона пропагує філософію: “Тестуйте ваш код так, як ним користується реальний користувач” (тобто, не перевіряйте стан (State) зміної напряму, а перевіряйте, що побачив юзер на екрані).

// Button.js
export function Button({ label }) {
  return <button className="btn">{label}</button>;
}
// Button.test.js
import { render, screen } from "@testing-library/react";
import { Button } from "./Button";

test("Кнопка мала б рендерити переданий текст", () => {
  // Віртуально малюємо компонент у пам'яті комп'ютера (без реального браузера)
  render(<Button label="Зберегти профайл" />);

  // Шукаємо кнопку на екрані за її текстом
  const buttonElement = screen.getByText(/Зберегти профайл/i);

  // Перевіряємо, що такий елемент фізично існує в DOM
  expect(buttonElement).toBeInTheDocument();
});

Для тестів, де треба “клікнути” кнопку (імітувати дії), RTL надає об’єкт fireEvent або бібліотеку userEvent.

import userEvent from "@testing-library/user-event";

test("Зміна лічильника при кліку", async () => {
  render(<Counter />);
  const button = screen.getByRole("button", { name: "Клікни мене" });

  // Імітуємо клік користувача
  await userEvent.click(button);

  // Очікуємо, що цифра на екрані змінилася з 0 на 1
  expect(screen.getByText("Рахунок: 1")).toBeInTheDocument();
});

4. End-to-End (Наскрізне) тестування: Cypress

Модульні тести Jest — швидкі, але вони не гарантують, що кнопка не перекрита невидимим div-ом у реальному браузері, або що сервер не впав з помилкою 500. Е2Е тести перевіряють весь потік від початку до кінця. Сьогодні лідером у цьому сегменті є Cypress (або його конкурент Playwright).

Cypress запускає повноцінний браузер (Chrome) і керує ним через код. Ви можете дивитися відео або робити скріншоти процесу.

// cypress/e2e/login.cy.js

describe("Форма Авторизації", () => {
  it("Користувач може успішно залогінитись", () => {
    // 1. Справжній браузер переходить на вашу сторінку
    cy.visit("http://localhost:3000/login");

    // 2. Робот знаходить input за атрибутом id або класом і друкує текст
    cy.get("#email").type("user@test.com");
    cy.get('input[name="password"]').type("123456");

    // 3. Знаходить кнопку і вдаряє по ній лівим кліком миші
    cy.get('button[type="submit"]').click();

    // 4. Перевірка: чи змінився URL в адресному рядку браузера на /dashboard
    cy.url().should("include", "/dashboard");

    // 5. Перевірка: чи з'явилося привітання після редиректу
    cy.contains("Ласкаво просимо, User!").should("be.visible");
  });
});

Cypress чудовим тим, що він автоматично “чекає” (Auto-waiting). Вам не потрібно писати setTimeout(), чекаючи поки React завантажить дані після кліку. Якщо елемент не з’явився одразу, Cypress почекає 4 секунди, перевіряючи DOM кожні 10 мілісекунд, перш ніж здатися і видати помилку тесту.

5. TDD (Test-Driven Development) — Розробка через тестування

TDD — це інженерна методологія, яка перевертає процес програмування з ніг на голову.

Класичний підхід: Написати код -> Написати тести на цей код. Підхід TDD: Написати тести -> Зрозуміти, що код не працює (Red) -> Написати код -> Тест пройдено (Green) -> Покращити якість коду (Refactor).

Цикл TDD (Червоний - Зелений - Рефакторинг):

  1. Red (Червоний): Ви пишете тест на функцію фінансового звіту, хоча самої функції ще не існує. Тест гарантовано “падає” (світиться червоним).
  2. Green (Зелений): Ви пишете найбрудніший, найпримітивніший код цієї функції, ТІЛЬКИ ЩОБ тест пройшов (щоб повернув потрібну цифру). Вона світиться зеленим.
  3. Refactor (Жовтий): Тепер, маючи зелений тест-запобіжник, ви красиво переписуєте і оптимізуєте свій брудний код. Якщо ви випадково зламаєте логіку, тест миттєво знову стане червоним.

TDD змушує думати про “Архітектуру та Інтерфейси” до написання самої реалізації і гарантує 100% покриття коду тестами (Code Coverage).

Висновки

  1. Автоматизоване тестування — це гарантія безпеки розробника. “Зелені” тести дозволяють вносити зміни у старий код без страху зламати весь проект (регресія).
  2. Піраміда тестування складається з трьох рівнів: E2E тести (мало, дорого, повільно), Інтеграційні (більше, надійно), Unit-тести (дуже багато, миттєво, перевіряють деталі).
  3. Jest — золотий стандарт для Unit-тестування логіки (describe, test, expect(x).toBe(y)).
  4. React Testing Library (RTL) тестує не внутрішній стан useState, а те, що по бачить користувач на екрані. Ми шукаємо текст і кнопки віртуальному DOM (screen.getByText).
  5. Cypress створює E2E-тести: справжній скрипт, що підіймає браузер Google Chrome, переходить за URL-адресами (cy.visit()), вводить дані в поля форми та перевіряє візуальний результат сторінки.
  6. TDD — це філософія “спочатку тест, потім код”. Вона змушує розробника проектувати зручну та закриту систему за схемою “Red-Green-Refactor”.

Джерела

  1. Jest Official Documentation
  2. React Testing Library — філософія тестування UI-компонентів.
  3. Cypress Docs: Writing Your First E2E Test — гайд від найшвидшого браузерного тестувальника.

Запитання для самоперевірки

  1. Згадайте Піраміду тестування. Який відсоток тестів мають займати Unit та E2E тести, і чому E2E не роблять на кожну маленьку функцію?
  2. Як називаються три ключові кроки будь-якого правильного тесту (Підготовка, Дія, Перевірка) англійською мовою (правило трьох ‘A’)?
  3. У коді Jest ми бачимо expect(someVar).toBe(5). Який аналог цієї функції буде використано для порівняння двох складних об’єктів з ключами (toEqual)?
  4. В чому полягає філософія React Testing Library (RTL)? Чому він шукає елементи на екрані за їхнім текстом getByText, а не за ім’ям CSS класу (наприклад .login-button)?
  5. Назвіть 3 базові команди Cypress для взаємодії з веб-сторінкою: відкрити URL, знайти елемент і натиснути його, набрати текст у input.
  6. Розшифруйте абревіатуру TDD та поясніть 3 фази циклу “Червоний-Зелений-Рефакторинг”.