10.6 Vitest תרגול
תרגול - קונספט בדיקות ו-Vitest - Testing Concepts and Vitest¶
תרגיל 1 - בדיקות לפונקציות מחרוזת¶
כתבו את הפונקציות הבאות ובדיקות מלאות לכל אחת:
capitalize(str)- הופכת את האות הראשונה לגדולהslugify(str)- ממירה מחרוזת ל-URL slug (אותיות קטנות, מקפים במקום רווחים, ללא תווים מיוחדים)truncate(str, maxLength)- מקצרת מחרוזת ומוסיפה "..." אם היא ארוכה מ-maxLengthcountWords(str)- סופרת מילים במחרוזת
כתבו לפחות 4 בדיקות לכל פונקציה, כולל מקרי קצה (מחרוזת ריקה, null, תווים מיוחדים).
תרגיל 2 - בדיקות לפונקציות מערך¶
כתבו את הפונקציות הבאות ובדיקות:
// 1. הסרת כפילויות
function unique<T>(arr: T[]): T[];
// 2. קיבוץ לפי מפתח
function groupBy<T>(arr: T[], key: keyof T): Record<string, T[]>;
// 3. מיון לפי כמה שדות
function sortBy<T>(arr: T[], ...keys: (keyof T)[]): T[];
// 4. שטוח מערך מקונן
function flatten<T>(arr: (T | T[])[]): T[];
// 5. חלוקה לקבוצות (chunks)
function chunk<T>(arr: T[], size: number): T[][];
כתבו את הפונקציות ולכל אחת לפחות 3 בדיקות.
תרגיל 3 - בדיקות TDD לעגלת קניות¶
השתמשו בגישת TDD לבניית מודול עגלת קניות. כתבו את הבדיקות קודם, ואז את הקוד.
הממשק:
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface Cart {
items: CartItem[];
addItem(item: Omit<CartItem, 'quantity'>, quantity?: number): void;
removeItem(id: string): void;
updateQuantity(id: string, quantity: number): void;
getTotal(): number;
getItemCount(): number;
clear(): void;
getItem(id: string): CartItem | undefined;
}
כללי עסקיים:
- הוספת מוצר שכבר קיים מעדכנת את הכמות
- כמות לא יכולה להיות שלילית
- הסרת מוצר שלא קיים לא גורמת לשגיאה
- סה"כ = סכום (מחיר * כמות) לכל פריט
תרגיל 4 - בדיקות לקוד אסינכרוני¶
נתונות הפונקציות הבאות. כתבו בדיקות מלאות:
// utils/api.ts
export async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`User ${id} not found`);
}
return response.json();
}
export async function fetchWithRetry(
url: string,
retries: number = 3,
delay: number = 1000
): Promise<Response> {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (response.ok) return response;
} catch (error) {
if (i === retries - 1) throw error;
}
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error(`Failed after ${retries} retries`);
}
export function debounce<T extends (...args: any[]) => void>(
fn: T,
delay: number
): T {
let timeoutId: ReturnType<typeof setTimeout>;
return ((...args: any[]) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
}) as T;
}
בדקו:
- fetchUser: תגובה מוצלחת, שגיאה 404, שגיאת רשת
- fetchWithRetry: הצלחה בניסיון ראשון, הצלחה אחרי retry, כישלון אחרי כל הניסיונות
- debounce: קריאה בודדת, קריאות מרובות (רק האחרונה רצה), delay נכון
תרגיל 5 - בדיקות לפונקציית Validator¶
כתבו validator מלא לטופס הרשמה ובדיקות מקיפות:
interface RegistrationForm {
username: string;
email: string;
password: string;
confirmPassword: string;
age: number;
agreeToTerms: boolean;
}
interface ValidationResult {
valid: boolean;
errors: Record<string, string>;
}
function validateRegistration(form: RegistrationForm): ValidationResult;
כללי validation:
- username: 3-20 תווים, רק אותיות, מספרים וקו תחתון
- email: פורמט אימייל תקין
- password: לפחות 8 תווים, אות גדולה, אות קטנה, מספר
- confirmPassword: חייב להיות זהה ל-password
- age: 13-120
- agreeToTerms: חייב להיות true
תרגיל 6 - בדיקות עם Setup/Teardown¶
כתבו בדיקות למחלקת LocalStorage wrapper:
class Storage {
private prefix: string;
constructor(prefix: string = 'app') {
this.prefix = prefix;
}
set(key: string, value: any): void {
localStorage.setItem(`${this.prefix}:${key}`, JSON.stringify(value));
}
get<T>(key: string, defaultValue?: T): T | undefined {
const item = localStorage.getItem(`${this.prefix}:${key}`);
if (item === null) return defaultValue;
try {
return JSON.parse(item);
} catch {
return defaultValue;
}
}
remove(key: string): void {
localStorage.removeItem(`${this.prefix}:${key}`);
}
clear(): void {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(`${this.prefix}:`)) {
localStorage.removeItem(key);
}
});
}
has(key: string): boolean {
return localStorage.getItem(`${this.prefix}:${key}`) !== null;
}
}
השתמשו ב-beforeEach ו-afterEach לניקוי ה-localStorage בין בדיקות.
שאלות¶
- מה ההבדל בין בדיקת יחידה לבדיקת אינטגרציה? תנו דוגמה קונקרטית לכל אחת.
- הסבירו את תהליך TDD בשלושה שלבים. מה היתרון של לכתוב את הבדיקה קודם?
- מה ההבדל בין
toBeל-toEqual? מתי כל אחד מתאים? - למה חשוב להשתמש ב-fake timers בבדיקות? מה הייתה הבעיה בלעדיהם?
- מה ההבדל בין
beforeEachל-beforeAll? מתי משתמשים בכל אחד?