Ось готовий урок, створений спеціально за твоїм запитом у стилі CS50.
🎓 Тема: Тестування Angular: Unit-тести з Jasmine та Karma
Привіт, світе! 👋 Я радий бачити вас на цьому занятті.
Сьогодні ми не просто пишемо код. Сьогодні ми вчимося писати код, який захищає інший код. Ми поговоримо про Unit Testing (юніт-тестування) в Angular, використовуючи інструменти Jasmine та Karma.
1. 🔥 Вступ: Чому ми взагалі це робимо?
Уявіть ситуацію. Ви будуєте величезний хмарочос 🏢. Ви кладете цеглу за цеглою, поверх за поверхом. І ось, на 50-му поверсі, ви вирішуєте замінити вікно на першому поверсі. Ви міняєте скло... і раптом весь хмарочос розсипається, як картковий будиночок.
Звучить як нічний жах архітектора, правда? Але в програмуванні це трапляється щодня. Це називається регресія — коли нова зміна ламає щось старе, що чудово працювало.
Запитання до вас: Як часто ви фіксили один баг, а створювали два нових? (Я підіймаю руку — я робив це сотні разів).
Якби ми будували літак, ми б не запускали його в рейс з пасажирами, щоб перевірити, чи працюють двигуни, вірно? Ми б перевіряли кожну заклепку, кожен гвинтик окремо в ангарі.
Ось що таке Unit-тести: 1. Це ваш "ангар", де ви перевіряєте кожну деталь (компонент, сервіс) ізольовано. 2. Це ваша "страховка", яка дозволяє спати спокійно, знаючи, що ваші зміни не "завалили" продакшн.
Без тестів ви — сапер, який працює навпомацки. З тестами ви — інженер з планшетом, у якого все під контролем.
2. 🧠 Теоретична база: Хто такі Jasmine та Karma?
В Angular тестування часто лякає новачків страшними словами. Давайте спростимо. У нас є два головних герої:
🎭 1. Jasmine (Джасмін) — Це Сценарист
Jasmine — це бібліотека (framework) для написання тестів. Вона дає нам мову, синтаксис. Уявіть, що ви пишете сценарій для п'єси: * "Ось сцена (describe)..." * "У цій сцені актор має зробити сальто (it)..." * "Я очікую, що він приземлиться на ноги (expect)..."
Jasmine нічого не запускає. Вона просто описує, як має поводитися код.
🏃♂️ 2. Karma (Карма) — Це Режисер (Runner)
Karma — це інструмент, який бере ваш сценарій (Jasmine), відкриває браузер (Chrome, Firefox), запускає там ваш код і каже: "Так, сценарій виконано успішно" або "Ні, актор впав обличчям у підлогу".
Що "під капотом"? (Інтуїтивне розуміння)
Коли ви запускаєте ng test:
1. Angular компілює ваш код.
2. Karma відкриває вікно браузера.
3. Код виконується в цьому браузері.
4. Результати (зелені галочки або червоні хрестики) повертаються вам у консоль.
🔑 Словничок (запам'ятайте ці три слова):
describe(): Група тестів (наприклад, "Тести для Компонента Логіну").it(): Конкретний тест-кейс (наприклад, "Має показувати помилку, якщо пароль короткий").expect(): Перевірка (наприклад, "Очікую, щоerrorMessageдорівнює 'Too short'").
3. 🧪 Приклади: Від простого до реального
Приклад 1: Чиста логіка (Без Angular)
Уявіть, у нас є функція, яка додає два числа. Що ви очікуєте, якщо передати туди 2 і 2? Правильно, 4. Давайте напишемо це мовою Jasmine.
// calculator.ts (Уявна функція)
function add(a, b) {
return a + b;
}
// calculator.spec.ts (Наш тест)
describe('Калькулятор', () => { // 1. Описуємо групу
it('має повертати 4, якщо додати 2 і 2', () => { // 2. Описуємо дію
const result = add(2, 2);
expect(result).toBe(4); // 3. Перевіряємо результат
});
});
Це було просто, еге ж? toBe — це як суворе "дорівнює".
Приклад 2: Angular Компонент
А тепер реальний світ. У нас є компонент UserComponent, який має властивість username.
// user.component.ts
export class UserComponent {
username = 'Гість';
}
Як нам протестувати це? Angular дає нам суперсилу — TestBed.
Аналогія: TestBed — це як лабораторія або "пісочниця". Ми створюємо компонент не в реальному додатку, а в стерильних умовах, де можемо його "мацати" з усіх боків.
// user.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserComponent } from './user.component';
describe('UserComponent', () => {
let component: UserComponent;
let fixture: ComponentFixture<UserComponent>;
beforeEach(async () => {
// Налаштовуємо "лабораторію"
await TestBed.configureTestingModule({
declarations: [ UserComponent ]
})
.compileComponents();
});
beforeEach(() => {
// Створюємо компонент перед кожним тестом
fixture = TestBed.createComponent(UserComponent);
component = fixture.componentInstance;
fixture.detectChanges(); // Запускаємо зміни (ngOnInit і т.д.)
});
// 👇 ВАШ ПЕРШИЙ СПРАВЖНІЙ ТЕСТ
it('має створити компонент', () => {
expect(component).toBeTruthy(); // Чи існує він взагалі?
});
it(`має мати ім'я 'Гість' за замовчуванням`, () => {
expect(component.username).toEqual('Гість');
});
});
Чому так? beforeEach гарантує, що перед кожним тестом ми маємо чистий, свіжий компонент. Ми не хочемо, щоб тест №1 забруднив дані для тесту №2.
Приклад 3: Взаємодія з HTML (DOM)
Давайте ускладнимо. При натисканні кнопки заголовок має змінитися. Ви очікуєте: Клік -> Текст змінюється.
<!-- user.component.html -->
<h1>{{ title }}</h1>
<button (click)="changeTitle()">Натисни мене</button>
// user.component.ts
export class UserComponent {
title = 'Привіт';
changeTitle() {
this.title = 'Па-па!';
}
}
Тест:
it('має змінити заголовок після кліку', () => {
// 1. Перевіряємо початковий стан
expect(component.title).toBe('Привіт');
// 2. Симулюємо дію (виклик методу)
component.changeTitle();
// 3. Змушуємо Angular оновити HTML
fixture.detectChanges();
// 4. Отримуємо елемент з HTML
const compiled = fixture.nativeElement as HTMLElement;
const h1 = compiled.querySelector('h1');
// 5. Перевіряємо, що бачить користувач
expect(h1?.textContent).toContain('Па-па!');
});
Важливий нюанс: Angular не оновлює HTML миттєво в тестах. Ми маємо вручну сказати fixture.detectChanges(). Це як сказати браузеру: "Ей, перемалюй картинку!".
4. 🛠 Практична частина
Прийшов час забруднити руки! Відкрийте свій IDE або уявіть, що ви на співбесіді.
Завдання 1: Математика
У вас є метод calculateDiscount(price: number, percent: number). Напишіть тест (блок it), який перевіряє, що calculateDiscount(100, 20) повертає 80.
Завдання 2: Логіка перемикача
У компоненті є булева змінна isOn = false і метод toggle(), який змінює її на протилежну.
Напишіть тест:
1. Викликати toggle().
2. Перевірити, що isOn стало true.
3. Викликати toggle() ще раз.
4. Перевірити, що isOn знову false.
Завдання 3: Знайди баг (Дебаггінг)
Тест падає з помилкою: Expected 'User' to equal 'Admin'.
Код компонента: role = 'User'.
Код тесту: expect(component.role).toEqual('Admin');.
Питання: Що треба виправити — код чи тест? (Підказка: залежить від бізнес-вимог, але зазвичай ми підганяємо код під вимоги).
Завдання 4: Міні-кейс
Створіть тест для кнопки "Увійти".
* Початковий текст кнопки: "Login".
* Після виклику методу login(), змінна isLoading стає true.
* Перевірте, що змінна змінилася.
Завдання 5: А що, якщо...? Що буде, якщо в Завданні 1 передати від'ємний відсоток знижки? Подумайте: Як досвідчений розробник, ви маєте написати тест і на цей випадок (наприклад, функція має кинути помилку або повернути початкову ціну).
5. 💡 Мислення як у розробника
Ось кілька секретів, які відрізняють "джуна" від "сеньйора" у тестуванні:
-
Не тестуйте фреймворк.
- Помилка: Писати тест, який перевіряє, чи працює
*ngIf. Angular команда в Google вже це протестувала. - Правильно: Тестуйте, чи ваша логіка правильно перемикає умову для
*ngIf.
- Помилка: Писати тест, який перевіряє, чи працює
-
Red-Green-Refactor.
- Це мантра TDD (Test Driven Development).
- Спочатку напишіть тест, який падає (Red).
- Потім напишіть код, щоб тест пройшов (Green).
- Потім покращте код (Refactor), не ламаючи тест.
-
Тести — це документація.
- Коли ви приходите в новий проєкт, не читайте код. Читайте
describeтаitу тестах. Вони розкажуть вам, що має робити система, людською мовою.
- Коли ви приходите в новий проєкт, не читайте код. Читайте
6. 🧩 Підсумок
Ну що, друзі, як відчуття? Ви тільки що зробили крок від "я сподіваюся, воно працює" до "я знаю, що воно працює".
Що ви тепер вмієте:
* ✅ Розумієте різницю між Jasmine (сценарій) та Karma (сцена).
* ✅ Вмієте написати базовий тест для класу та методу.
* ✅ Знаєте, як використовувати TestBed для перевірки Angular-компонентів.
* ✅ Вмієте перевіряти, чи змінився текст у HTML.
Наступного разу: А що робити, коли компонент робить запит на сервер за даними? Ми ж не хочемо реально стукати на сервер під час тестів? На наступному уроці ми вивчимо Mocks & Spies (Моки та Шпигуни) — як обдурити компонент і підсунути йому фейкові дані. Це буде справжня шпигунська гра! 🕵️♂️
А поки що — пишіть тести, ламайте їх і робіть зеленими знову. Це і є розробка! Побачимось! 👋
Якщо ви готові продовжувати:
Скажіть "Згенеруй завдання для моків", і ми підемо далі!