GammaCyber

Testing ו-MVP

פיתוח Fullstack מתקדם · 5–6 שעות

// סקירה

Testing, MVP ו-Git Workflow

שיעור כפול: חצי ראשון — Testing אוטומטי עם Jest ו-React Testing Library. חצי שני — סדנת קוד: מהלומד למפתח — MVP, Debugging מקצועי ו-README מנצח.

✓נושאי השיעור
  • Testing Pyramid — Unit, Integration, E2E: כמה מכל סוג?
  • Jest: describe, test, expect — מבנה בדיקה בסיסית
  • Matchers: toBe, toEqual, toContain, toThrow
  • React Testing Library: render, screen, queries
  • userEvent > fireEvent — סימולציית אינטראקציה אמיתית
  • Async Tests: findBy, waitFor — בדיקת קוד אסינכרוני
  • Mocking: jest.fn(), jest.mock() — בידוד תלויות
  • MVP — בניית הפיצ'ר המינימלי End-to-End
  • Git Workflow — Branches, Commits, Pull Requests
  • Debugging מקצועי — DevTools, Logs, Stack Trace
// פרק 1

למה Testing? הבעיה שבדיקות פותרות

בפרויקט קטן אפשר לבדוק ידנית. אבל בפרויקט של 50 קומפוננטות — כל שינוי קטן יכול לשבור משהו שלא ידעת שקיים.

Regression Bug = באג שנשבר בגלל שינוי שלכאורה לא קשור. בדיקות אוטומטיות תופסות אותם לפני שהמשתמש רואה אותם.

בלי בדיקותעם בדיקות
שינוי קוד → פחד לפרוסשינוי קוד → CI/CD מוודא שהכל עובד
Regression Bugs גלויים רק ב-ProductionRegression Bugs נתפסים מיד ב-Push
Refactoring מסוכןRefactoring בטוח — הבדיקות יאמרו אם שברת
תיעוד מיושן לא מעודכןהבדיקות הן תיעוד חי של ההתנהגות
💡 Testing Pyramid
Unit Tests (בסיס): הרבה — מהירות, זולות, מבודדות. בודקות פונקציה אחת.
Integration Tests (אמצע): בינוני — בודקות שמספר יחידות עובדות יחד.
E2E Tests (פסגה): מעט — איטיות, יקרות, מדמות משתמש אמיתי (Cypress, Playwright).

לפרויקט בגאמא: Unit + Component Tests מספיקים להרשים.
✏תרגול עצמי2 שאלות
שאלה 1.1
מה זה Regression Bug?
✓ תשובה: א) — Regression Bug הוא כשפיצ'ר שעבד תקין פתאום נשבר בגלל שינוי לא קשור. לדוגמה: תיקנת את כפתור הכניסה, ובמקרה שברת את כפתור ההרשמה. בדיקות אוטומטיות תופסות זאת מיד.
שאלה 1.2
לפי Testing Pyramid, איזה סוג בדיקה כדאי לכתוב הכי הרבה?
✓ תשובה: א) — Unit Tests הם הבסיס של הפירמידה: מהירות לכתיבה, מהירות להרצה, קל לאבחן כשהן נכשלות. E2E איטיות ויקרות — כותבים אותן רק לתהליכים קריטיים.
// פרק 2

Jest — יסודות

Jest הוא Test Runner — מוצא קבצי בדיקה, מריץ אותם, ומדווח על תוצאות. CRA ו-Vite כוללים אותו מהקופסה.

מבנה בדיקה בסיסית

// utils/math.test.js (או math.spec.js — שניהם תקינים) // describe: מקבץ בדיקות קשורות describe('פונקציות מתמטיות', () => { // test / it — בדיקה אחת test('חיבור שני מספרים', () => { const result = add(2, 3); expect(result).toBe(5); // Assertion — מה אנחנו מצפים }); it('מחזיר שגיאה על קלט לא-מספרי', () => { expect(() => add('a', 3)).toThrow('קלט לא תקין'); }); }); // הרצה: npx jest (או npm test ב-CRA)

Matchers — איך בודקים ערכים

Matcherשימושדוגמה
toBeהשוואה מדויקת (===)expect(2+2).toBe(4)
toEqualהשוואת אובייקטים/מערכיםexpect(obj).toEqual({a:1})
toContainמכיל ערך (מערך/string)expect([1,2]).toContain(2)
toHaveLengthאורך מסויםexpect([1,2,3]).toHaveLength(3)
toThrowזורק שגיאהexpect(fn).toThrow('msg')
not.toBeשלילת כל Matcherexpect(1).not.toBe(2)

Unit Test אמיתי — Arrange-Act-Assert

// utils/todos.js export function filterTodos(todos, filter) { if (filter === 'active') return todos.filter(t => !t.done); if (filter === 'done') return todos.filter(t => t.done); return todos; } // utils/todos.test.js import { filterTodos } from './todos'; const mockTodos = [ { id: 1, text: 'ללמוד React', done: false }, { id: 2, text: 'לכתוב בדיקות', done: true }, { id: 3, text: 'לפרוס פרויקט', done: false }, ]; describe('filterTodos', () => { test('filter=all מחזיר את כולם', () => { // Arrange: נתונים מוכנים (mockTodos) // Act: הפעל const result = filterTodos(mockTodos, 'all'); // Assert: בדוק expect(result).toHaveLength(3); }); test('filter=active מחזיר רק לא-גמורים', () => { const result = filterTodos(mockTodos, 'active'); expect(result).toHaveLength(2); expect(result.every(t => !t.done)).toBe(true); }); test('filter=done מחזיר רק גמורים', () => { const result = filterTodos(mockTodos, 'done'); expect(result).toHaveLength(1); expect(result[0].id).toBe(2); }); });
🔑חשוב לגאמא: Arrange-Act-Assert
  • Arrange: הכן את הנתונים (mockTodos)
  • Act: הפעל את הפונקציה (filterTodos(mockTodos, 'active'))
  • Assert: בדוק את התוצאה (expect(result).toHaveLength(2))
  • בסדנה — בדיקה שכתובה לפי AAA נראית מקצועית ומובנת
✏תרגול עצמי3 שאלות
שאלה 2.1
מה ההבדל בין toBe לבין toEqual?
✓ תשובה: ג) — expect({a:1}).toBe({a:1}) נכשל כי זה שני אובייקטים שונים בזיכרון. expect({a:1}).toEqual({a:1}) עובר כי toEqual משווה את התוכן.
שאלה 2.2
מה יהיה תוצאת הבדיקה הבאה?
expect([1,2,3]).toHaveLength(2)
✓ תשובה: ד) — toHaveLength בודק את מאפיין .length. [1,2,3].length = 3, לא 2 — הבדיקה נכשלת. לבדוק אם המערך מכיל 2 השתמש ב-toContain(2).
שאלה 2.3
בשלב "Arrange" בבדיקה, מה עושים?
✓ תשובה: ג) — AAA: Arrange = הכנה, Act = הפעלה, Assert = בדיקה. ב-Arrange מכינים הכל לפני שמריצים את הקוד שבודקים.
// פרק 3

React Testing Library — בדיקת קומפוננטות

RTL בנויה על עיקרון אחד: בדוק מה המשתמש רואה ועושה — לא את המימוש הפנימי. אל תבדוק state פנימי, אל תבדוק שמות פונקציות.

render ו-screen

import { render, screen } from '@testing-library/react'; import Button from './Button'; test('מציג את ה-label של הכפתור', () => { // render: מרנדר קומפוננטה לתוך DOM וירטואלי render(<Button label='שמור' => {}} />); // screen: API לגישה ל-DOM שרונדר const button = screen.getByText('שמור'); expect(button).toBeInTheDocument(); }); test('כפתור disabled לא ניתן ללחיצה', () => { render(<Button label='שמור' => {}} disabled />); expect(screen.getByText('שמור')).toBeDisabled(); });

Queries — איך מוצאים elements

Queryמחזירזורק אם לא נמצא?מתי משתמשים
getBy...elementכן — מידכשElement חייב להיות שם
queryBy...element/nullלאכשרוצים לבדוק שElement לא קיים
findBy...Promiseכן — asyncכשElement מופיע אחרי המתנה
getAllBy...element[]כן אם 0כשצפויים כמה elements
💡 ByRole — הדרך המועדפת
getByRole('button', {name: 'שמור'}) עדיף על getByText('שמור').
ByRole בודק גם נגישות (Accessibility) — button, link, heading, checkbox, textbox.
סדר עדיפויות: ByRole > ByLabelText > ByPlaceholder > ByText > ByTestId

userEvent — סימולציית אינטראקציה

import userEvent from '@testing-library/user-event'; test('הוספת Todo חדש', async () => { render(<TodoApp />); const input = screen.getByPlaceholderText('הוסף משימה...'); const button = screen.getByRole('button', { name: 'הוסף' }); // הקלד טקסט — userEvent מדמה אינטראקציה אמיתית await userEvent.type(input, 'ללמוד Testing'); await userEvent.click(button); // בדוק שהטקסט מופיע ברשימה expect(screen.getByText('ללמוד Testing')).toBeInTheDocument(); // בדוק שה-input התרוקן expect(input).toHaveValue(''); });
הרחבה userEvent לעומת fireEvent ›

fireEvent.click(): שולח אירוע DOM בסיסי — מהיר אבל לא מדמה משתמש אמיתי.

userEvent.click(): מדמה את כל רצף האירועים שמשתמש אמיתי מייצר (mousedown, focus, mouseup, click). ב-userEvent כל הפונקציות הן async — חייב await.

המלצה: userEvent תמיד — אלא אם fireEvent מהיר יותר בבדיקת edge cases.

✏תרגול עצמי3 שאלות
שאלה 3.1
רוצים לבדוק שאחרי לחיצה על "מחק", הפריט לא קיים בDOM. איזו query מתאימה?
✓ תשובה: א) — queryByText מחזיר null אם לא נמצא (לא זורק). אז: expect(screen.queryByText('פריט')).not.toBeInTheDocument(). getBy היה זורק שגיאה ומפיל את הבדיקה.
שאלה 3.2
מדוע RTL ממליצה על getByRole יותר מ-getByText?
✓ תשובה: ב) — getByRole('button', {name: 'שמור'}) בודק שיש element עם role=button (ולא div עם onClick). זה מבטיח שהאפליקציה נגישה לכלים עזר (Screen Readers) ולא רק ויזואלית.
שאלה 3.3
מה עיקרון הליבה של React Testing Library?
✓ תשובה: ב) — הפילוסופיה של RTL: "The more your tests resemble the way your software is used, the more confidence they can give you." בדיקת state.count פנימי = ImplementationDetails שצריך להימנע ממנו.
// פרק 4

Async Tests ו-Mocking

קומפוננטות שעושות fetch לא מציגות נתונים מיד. RTL מספקת כלים לבדיקת מצבים אסינכרוניים, ו-Mocking מאפשר בידוד מ-API אמיתי.

findBy ו-waitFor

import { render, screen, waitFor } from '@testing-library/react'; test('מציג שם משתמש אחרי טעינה', async () => { render(<UserProfile userId={1} />); // בתחילה — Spinner מוצג expect(screen.getByText('טוען...')).toBeInTheDocument(); // findByText: מחכה עד שElement מופיע (timeout: 1000ms) const name = await screen.findByText('Alice'); expect(name).toBeInTheDocument(); // Spinner נעלם expect(screen.queryByText('טוען...')).not.toBeInTheDocument(); }); // waitFor: מחכה עד שתנאי מתקיים test('מציג Error אחרי כשל', async () => { render(<UserProfile userId={999} />); await waitFor(() => { expect(screen.getByText(/שגיאה/)).toBeInTheDocument(); }); });

Mock של fetch — בידוד מ-API אמיתי

// לפני הבדיקות — Mock גלובלי לfetch global.fetch = jest.fn(); beforeEach(() => { // הגדר מה fetch יחזיר בכל בדיקה fetch.mockResolvedValueOnce({ ok: true, json: async () => ({ id: 1, name: 'Alice', email: 'alice@test.com' }), }); }); afterEach(() => { jest.clearAllMocks(); // נקה Mocks אחרי כל בדיקה }); test('מציג פרטי משתמש מה-API', async () => { render(<UserProfile userId={1} />); expect(await screen.findByText('Alice')).toBeInTheDocument(); expect(screen.getByText('alice@test.com')).toBeInTheDocument(); // בדוק שfetch נקרא עם ה-URL הנכון expect(fetch).toHaveBeenCalledWith('/api/users/1'); expect(fetch).toHaveBeenCalledTimes(1); });

jest.fn() — Mock לפונקציה

test('קורא ל-onDelete כשלוחצים מחק', async () => { const mockOnDelete = jest.fn(); // פונקציה מזויפת שמתעדת קריאות render(<TodoItem todo={{ id: 1, text: 'משימה', done: false }} />); await userEvent.click(screen.getByRole('button', { name: 'מחק' })); expect(mockOnDelete).toHaveBeenCalledTimes(1); expect(mockOnDelete).toHaveBeenCalledWith(1); // עם ה-id הנכון });
! טעות נפוצה — בדיקות שמשפיעות אחת על השנייה ›

כשבדיקה שינתה State גלובלי (localStorage, Mock) ולא ניקתה — הבדיקה הבאה מקבלת מצב לא נקי.

תסמין: הבדיקה עוברת כשמריצים אותה לבד אבל נכשלת עם כל הבדיקות.

beforeEach(() => { localStorage.clear(); jest.clearAllMocks(); // מאפס call history של כל המocks });
כלל: כל בדיקה חייבת להיות עצמאית לחלוטין — לא תלויה בסדר ההרצה.
✏תרגול עצמי3 שאלות
שאלה 4.1
מה ההבדל בין getByText, queryByText ו-findByText?
✓ תשובה: ד) — שלוש queries שונות למצבים שונים: getBy לחובה, queryBy לאופציונלי, findBy לאסינכרוני. בחירת הלא-נכונה תגרום לבדיקה שנכשלת מסיבות לא נכונות.
שאלה 4.2
למה צריך jest.clearAllMocks() ב-beforeEach?
✓ תשובה: ב) — אחרי בדיקה א' שה-mock נקרא פעם אחת, בדיקה ב' תראה שהmock נקרא פעמיים (א'+ב'). clearAllMocks() מאפס את הספירה לפני כל בדיקה.
שאלה 4.3
מה עדיף: data-testid או getByRole?
✓ תשובה: ד) — סדר עדיפויות: ByRole > ByLabelText > ByPlaceholder > ByText > ByTestId. data-testid הוא מוצא אחרון — מוסיף attrs לקוד רק לטובת בדיקות, ולא בודק נגישות.
// פרק 5 · סדנת קוד

MVP — מהלומד למפתח

הגיע הזמן לחבר הכל למוצר אחד עובד End-to-End. נהפוך מ"לומדים" ל"מפתחים" — MVP, Debugging מקצועי, ו-README שמרשים בוחנים.

🎯 מה בוחנים מחפשים לראות
כיצד בא לידי ביטוישלב במיוןנושא
הסבר מעבר המידע מUI לשרת למסד ובחזרהסדנה וראיונותפיצ'ר End-to-End
איתור תקלות בזמן אמת, שימוש ב-Logsסדנה מעשיתDebugging
Branching מסודר + README מקצועי ב-GitHubראיון מקצועיGit Workflow

MVP — בניית הפיצ'ר המינימלי

הטעות הנפוצה: לנסות לבנות הכל בו-זמנית. זה מתכון לכישלון.

גישת MVP: בנה את הגרסה הרזה ביותר שמספקת את הערך המרכזי. תכונות "Nice to have" — לאחר שה-Core עובד.

💡 שבירת משימה ל-30 דקות
"הוסף אימות משתמשים" = משימה של שבוע.
שבירה נכונה: ← טופס Login (30 דק') ← Endpoint POST /login (30 דק') ← JWT Token (30 דק') ← Protected Route (30 דק')

Git Workflow — כמו מהנדס

# כל פיצ'ר חדש = Branch נפרד git checkout -b feature/user-auth # Commits קטנים עם הודעות משמעותיות git commit -m "feat: add login form validation" git commit -m "fix: prevent empty password submission" git commit -m "feat: connect login to /api/auth endpoint" # לאחר שהפיצ'ר עובד — מיזוג ל-main git checkout main git merge feature/user-auth

Debugging מקצועי — מתודולוגיה מדעית

הרחבה שלבי Debugging — חיפוש בינארי ›

1. בדוק את נקודת החיבור (Network Tab): האם הבקשה יוצאת? האם השרת מקבל? מה קוד הסטאטוס?

2. בדוק את ה-Payload: מה בדיוק נשלח? מה בדיוק מוחזר? (DevTools → Network → Request/Response)

3. קרא את ה-Stack Trace: השגיאה בקונסול מצביעה על שורה מדויקת — קרא מלמטה למעלה.

4. בידוד ידני: בדוק כל שלב בנפרד עם console.log או Postman.

// באג נפוץ — שכחת await מול MongoDB // ❌ קוד פגום — שולח Promise, לא נתונים app.get('/api/users', (req, res) => { const allUsers = User.find({}); // חסר await! res.json(allUsers); // שולח [object Promise] }); // ✅ קוד תקין app.get('/api/users', async (req, res) => { try { const allUsers = await User.find({}); res.json(allUsers); } catch (err) { res.status(500).json({ error: err.message }); } });

README מנצח

# 🛡️ Todo Security App מערכת ניהול משימות מאובטחת עם אימות JWT ## Tech Stack - **Frontend:** React 18, TypeScript, Redux Toolkit - **Backend:** Node.js, Express, JWT - **Database:** MongoDB (Mongoose) ## התקנה ```bash # Clone git clone https://github.com/username/todo-app # התקן תלויות npm install cd client && npm install # הגדר משתני סביבה cp .env.example .env # הרץ npm run dev ``` ## API Endpoints | Method | Endpoint | Description | |--------|----------|-------------| | POST | /api/auth/login | התחברות | | GET | /api/todos | שליפת משימות | | POST | /api/todos | יצירת משימה |
✏תרגול עצמי2 שאלות
שאלה 5.1
הפרויקט שלך מחזיר אובייקט ריק מהשרת. מה הצעד הראשון ב-Debugging?
✓ תשובה: ד) — Network Tab מראה בדיוק מה נשלח ומה התקבל. כך מאתרים את נקודת הכשל: האם הבעיה ב-Frontend (בקשה שגויה), ב-Backend (שגיאת שרת), או ב-DB (נתונים ריקים).
שאלה 5.2
מדוע חשוב לעבוד עם Branches בגיט ולא לכתוב הכל ישירות ל-main?
✓ תשובה: ג) — main = קוד יציב שעובד תמיד. כל פיצ'ר חדש מתפתח ב-Branch נפרד. אם שברת משהו — פשוט לא ממזגים, ו-main נשמר תקין. מראיינים בגאמא בודקים את ה-Git History שלך.
// פרק 6

Testing עם Redux ו-Context

קומפוננטות שמשתמשות ב-Redux Store צריכות Provider בזמן הבדיקה. Custom Render Wrapper פותר זאת בצורה אלגנטית.

// test-utils.tsx — Wrapper שמכיל את כל ה-Providers import { render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { configureStore } from '@reduxjs/toolkit'; import todosReducer from '../store/todosSlice'; // מייצר Store חדש לכל בדיקה — מניעת זיהום בין בדיקות function renderWithProviders(ui, preloadedState = {}) { const store = configureStore({ reducer: { todos: todosReducer }, preloadedState, }); function Wrapper({ children }) { return <Provider store={store}>{children}</Provider>; } return { store, ...render(ui, { wrapper: Wrapper }) }; } // שימוש בבדיקה: test('מציג Todos מה-Store', () => { const preloadedState = { todos: [{ id: 1, text: 'ללמוד Redux', done: false }], }; renderWithProviders(<TodoList />, preloadedState); expect(screen.getByText('ללמוד Redux')).toBeInTheDocument(); });
🔑חשוב לגאמא: preloadedState
  • renderWithProviders מקבל preloadedState — מצב ראשוני ל-Store
  • כך כל בדיקה מתחילה עם נתונים ידועים, ללא תלות בבדיקות אחרות
  • בסדנה — Custom Render Utility מראה שאתה מבין Testing מקצועי
✏תרגול עצמי2 שאלות
שאלה 6.1
מדוע צריך ליצור Store חדש לכל בדיקה ב-renderWithProviders?
✓ תשובה: ד) — אם Store משותף, בדיקה א' שמוסיפה Todo תשאיר אותו לבדיקה ב'. כל בדיקה חייבת להתחיל עם State נקי וידוע. Store חדש לכל בדיקה מבטיח בידוד מלא.
שאלה 6.2
מה יקרה אם תרנדר קומפוננטה שמשתמשת ב-useSelector ללא Provider?
✓ תשובה: ב) — useSelector מחפש React Context של Redux. ללא Provider, ה-Context לא קיים ו-RTL יזרוק שגיאה. הפתרון הוא renderWithProviders שמכיל Provider.
// אתגר מעשי

אתגר מעשי — בדיקות — Mocks ו-Test Runner

שיא השיעור: לבנות רכיב עובד עם זווית אבטחה. ההגשה נבדקת אוטומטית.

T2 · בסיסי250 נק'~18 דק'build
Spy/Mock (בסגנון jest.fn)
ממש מחלקה Spy המדמה את jest.fn(): Spy(return_value=x) יוצר spy; קריאה לו s(*args, **kwargs) מתעדת את הקריאה ומחזירה את return_value; s.call_count = מספר הקריאות; s.called_with(*args, **kwargs) מחזיר True אם ה-spy נקרא אי-פעם עם הארגומנטים האלה.
דוגמאות
s = Spy(return_value=99); s(1,2) → 99
s.call_count → 1
s.called_with(1, 2) → True
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס Spy/Mock (בסגנון jest.fn) ›
class Spy: def __init__(self, return_value=None): self.calls = []; self._rv = return_value def __call__(self, *args, **kwargs): self.calls.append((args, kwargs)); return self._rv @property def call_count(self): return len(self.calls) def called_with(self, *args, **kwargs): return (args, kwargs) in self.calls
T3 · בינוני350 נק'~22 דק'build
מנוע הרצת-בדיקות זעיר
ממש run_tests(tests) — לב של Jest. tests היא רשימת זוגות (name, fn). הרץ כל fn: אם לא נזרקה חריגה (כולל AssertionError) — עבר; אחרת נכשל. החזר {'passed':int, 'failed':int, 'failures':[שמות הבדיקות שנכשלו לפי הסדר]}.
דוגמאות
run_tests([('a',pass_fn),('b',fail_fn),('c',pass_fn)]) → {'passed':2, 'failed':1, 'failures':['b']}
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס מנוע הרצת-בדיקות זעיר ›
def run_tests(tests: list) -> dict: passed = 0; failures = [] for name, fn in tests: try: fn(); passed += 1 except Exception: failures.append(name) return {'passed': passed, 'failed': len(failures), 'failures': failures}
// סיכום

סיכום — שיעור 21

עברנו משלב הלמידה לשלב ההנדסה. Testing מקצועי + MVP + Git Workflow + README = פרויקט שמרשים בוחנים גאמא.

✓מה למדנו בשיעור 21
  • Testing Pyramid: Unit (הרבה) → Integration (בינוני) → E2E (מעט)
  • Jest: describe / test / expect — מבנה בדיקה בסיסית
  • Matchers: toBe, toEqual, toContain, toHaveLength, toThrow
  • Arrange-Act-Assert: הכנה, הפעלה, בדיקה — המבנה הנכון לכל test
  • RTL: render + screen — עיקרון: בדוק מה המשתמש רואה
  • Queries: getBy (חובה) | queryBy (אופציונלי) | findBy (async)
  • ByRole עדיף — בודק נגישות + פונקציונליות בו-זמנית
  • userEvent > fireEvent — מדמה אינטראקציה אמיתית, כל הפונקציות async
  • Mock: jest.fn() לפונקציה | jest.mock() לספרייה
  • beforeEach / afterEach: ניקוי Mocks ו-localStorage לפני/אחרי בדיקה
  • Redux Testing: renderWithProviders עם preloadedState לבידוד מלא
  • MVP: פיצ'ר ליבה End-to-End לפני תכונות "Nice to have"
  • Git Workflow: Branch לכל פיצ'ר + Commits משמעותיים + README מקצועי
📌 לפני שיעור 22 (Build ו-Deployment)
  1. כתוב בדיקות ל-filterTodos, addTodo, toggleTodo מהפרויקט שלך
  2. כתוב בדיקת Component ל-TodoItem — render, click, delete
  3. שיעור 22: Build, Deployment ו-CI/CD — מהקוד ל-Production
שאלות תרגול ותשובות (10)

שאלה 1.1 · מה זה Regression Bug?

  • א. באג שנשבר בגלל שינוי קוד שלכאורה אינו קשור, כלומר תכונה שעבדה תקין הפסיקה לעבוד, ובדיקות אוטומטיות תופסות אותו עוד לפני העלייה לייצור כאן
  • ב. באג שקיים מאז כתיבת הקוד הראשון: הנחה שגויה, אף ש-Regression הוא באג שנשבר בגלל שינוי לא קשור ותכונה שעבדה הפסיקה ולא באג מקורי ותיק כלשהו
  • ג. באג שנמצא רק ב-Production ולא בפיתוח: הנחה שגויה, אף ש-Regression הוא באג שנשבר בגלל שינוי לא קשור ותכונה שעבדה הפסיקה ולא נבדל לפי הסביבה
  • ד. שגיאת TypeScript שנמצאת בזמן קומפילציה: הנחה שגויה, אף ש-Regression הוא באג שנשבר בגלל שינוי לא קשור ותכונה שעבדה הפסיקה ולא שגיאת טיפוס
תשובה: א

✓ תשובה: א) — Regression Bug הוא כשפיצ'ר שעבד תקין פתאום נשבר בגלל שינוי לא קשור. לדוגמה: תיקנת את כפתור הכניסה, ובמקרה שברת את כפתור ההרשמה. בדיקות אוטומטיות תופסות זאת מיד.

שאלה 1.2 · לפי Testing Pyramid, איזה סוג בדיקה כדאי לכתוב הכי הרבה?

// utils/math.test.js  (או math.spec.js — שניהם תקינים)

// describe: מקבץ בדיקות קשורות
describe('פונקציות מתמטיות', () => {

  // test / it — בדיקה אחת
  test('חיבור שני מספרים', () => {
    const result = add(2, 3);
    expect(result).toBe(5);  // Assertion — מה אנחנו מצפים
  });

  it('מחזיר שגיאה על קלט לא-מספרי', () => {
    expect(() => add('a', 3)).toThrow('קלט לא תקין');
  });
});

// הרצה: npx jest  (או npm test ב-CRA)
  • א. Unit Tests, מכיוון שהם מהירים וזולים ומבודדים וקלים לתחזוקה, ולכן הם בסיס הפירמידה בעוד E2E יקרות ואיטיות ונכתבות לתהליכים קריטיים בלבד
  • ב. E2E Tests שכן הן הכי מדויקות ומדמות משתמש אמיתי: הנחה שגויה, אף שהן איטיות ויקרות והבסיס הוא Unit Tests המהירים והזולים והמבודדים בפירמידה
  • ג. Integration Tests שכן הן בודקות את כל המערכת יחד: הנחה שגויה, אף שהבסיס של הפירמידה הוא Unit Tests המהירים והזולים והקלים לתחזוקה בכמות רבה
  • ד. שוויון שכן כדאי כמות שווה מכל סוג בדיקה: הנחה שגויה, אף שהבסיס הוא Unit Tests שכותבים בכמות הגדולה ביותר בעוד E2E נכתבות מעט בלבד בפירמידה
תשובה: א

✓ תשובה: א) — Unit Tests הם הבסיס של הפירמידה: מהירות לכתיבה, מהירות להרצה, קל לאבחן כשהן נכשלות. E2E איטיות ויקרות — כותבים אותן רק לתהליכים קריטיים.

שאלה 2.1 · מה ההבדל בין toBe לבין toEqual?

  • א. אין ביניהם כל הבדל ושניהם זהים: הנחה שגויה, אף ש-toBe בודק זהות הפניה או ערך פרימיטיבי ו-toEqual בודק שוויון עמוק של תוכן אובייקטים ומערכים
  • ב. toEqual בודק זהות הפניה ו-toBe בודק ערכים: הנחה שגויה, אף שזהו ההיפך שכן toBe בודק זהות הפניה ו-toEqual בודק שוויון עמוק של תוכן בלבד
  • ג. toBe בודק זהות הפניה או ערך פרימיטיבי, ואילו toEqual בודק שוויון עמוק של תוכן, ולכן שני אובייקטים שונים בזיכרון יעברו רק ב-toEqual כאן
  • ד. toBe מיועד לפונקציות ו-toEqual לערכים: הנחה שגויה, אף שההבחנה היא ש-toBe בודק זהות הפניה ו-toEqual בודק שוויון עמוק של תוכן ולא לפי סוג
תשובה: ג

✓ תשובה: ג) — expect({a:1}).toBe({a:1}) נכשל כי זה שני אובייקטים שונים בזיכרון. expect({a:1}).toEqual({a:1}) עובר כי toEqual משווה את התוכן.

שאלה 2.2 · מה יהיה תוצאת הבדיקה הבאה? expect([1,2,3]).toHaveLength(2)

  • א. עוברת שכן המערך מכיל את הערך 2 בתוכו: הנחה שגויה, אף ש-toHaveLength בודק את האורך שהוא 3 ולא 2 ולבדיקת הכלה של ערך משמש toContain בפועל
  • ב. נזרקת שגיאה בזמן ריצה של הבדיקה: הנחה שגויה, אף שהבדיקה רצה ונכשלת שכן האורך של המערך הוא 3 ולא 2 ולא נזרקת שגיאת ריצה כלשהי במהלכה כאן
  • ג. עוברת שכן toHaveLength בודק אם 2 נמצא: הנחה שגויה, אף ש-toHaveLength בודק את מאפיין האורך שהוא 3 ולא את הכלת הערך 2 בתוך המערך הנבדק
  • ד. נכשלת, מכיוון ש-toHaveLength בודק את מאפיין האורך והאורך של המערך הוא 3 ולא 2, ולבדיקת הכלה של הערך 2 יש להשתמש ב-toContain בפועל כאן
תשובה: ד

✓ תשובה: ד) — toHaveLength בודק את מאפיין .length. [1,2,3].length = 3, לא 2 — הבדיקה נכשלת. לבדוק אם המערך מכיל 2 השתמש ב-toContain(2).

שאלה 3.1 · רוצים לבדוק שאחרי לחיצה על "מחק", הפריט לא קיים בDOM. איזו query מתאימה?

  • א. queryByText — מחזיר null כשאין אלמנט
  • ב. getByText — זורק שגיאה ומפיל את הבדיקה
  • ג. findByText — ממתין להופעת אלמנט
  • ד. getAllByText — זורק כשאין תוצאות
תשובה: א

✓ תשובה: א) — queryByText מחזיר null כשהאלמנט לא קיים, ולכן: expect(screen.queryByText('פריט')).not.toBeInTheDocument(). 🎯 דגש למבחן — שאילתות RTL: רק queryBy... מחזירות null ומתאימות לבדיקת אלמנט שאינו קיים. getBy... זורק שגיאה ויכשיל את הבדיקה, ו-findBy... נועד להמתנה לאלמנט שעתיד להופיע.

שאלה 3.2 · מדוע RTL ממליצה על getByRole יותר מ-getByText?

  • א. getByRole מהיר יותר מבחינת ביצועים בהרצה: הנחה שגויה, אף שהיתרון הוא ש-getByRole בודק גם נגישות ואלמנט עם role שגוי מפיל את הבדיקה בפועל
  • ב. מכיוון ש-getByRole בודק גם נגישות, ולכן אלמנט עם role שגוי יגרום לבדיקה להיכשל גם אם הטקסט נכון, וזה מבטיח שהאפליקציה נגישה לכלי עזר כאן
  • ג. getByText אינו עובד עם טקסט בעברית: הנחה שגויה, אף ש-getByText עובד עם עברית והיתרון של getByRole הוא בדיקת הנגישות של האלמנט הנבדק בפועל
  • ד. אין ביניהם כל הבדל וזו רק העדפה אישית: הנחה שגויה, אף ש-getByRole בודק גם נגישות ואלמנט עם role שגוי מפיל את הבדיקה מעבר להעדפה כלשהי כאן
תשובה: ב

✓ תשובה: ב) — getByRole('button', {name: 'שמור'}) בודק שיש element עם role=button (ולא div עם onClick). זה מבטיח שהאפליקציה נגישה לכלים עזר (Screen Readers) ולא רק ויזואלית.

שאלה 4.1 · מה ההבדל בין getByText, queryByText ו-findByText?

  • א. כולם זהים ורק התחביר שלהם שונה: הנחה שגויה, אף ש-getBy זורק כשאינו נמצא ו-queryBy מחזיר null ו-findBy ממתין באופן אסינכרוני להופעת האלמנט
  • ב. findBy הוא הכי מהיר ו-getBy הכי איטי: הנחה שגויה, אף שההבחנה היא ש-getBy זורק ו-queryBy מחזיר null ו-findBy ממתין ואינה עניין של מהירות כלל
  • ג. queryBy עובד רק עם CSS selectors בלבד: הנחה שגויה, אף ש-queryBy מחזיר null כשאינו נמצא וההבחנה בין השלושה היא באופן הטיפול בהיעדר תוצאה בפועל
  • ד. getBy מחזיר מיד וזורק אם אינו נמצא, queryBy מחזיר null אם אינו נמצא, ו-findBy ממתין באופן אסינכרוני עד שהאלמנט מופיע במסמך בסוף בפועל כאן
תשובה: ד

✓ תשובה: ד) — שלוש queries שונות למצבים שונים: getBy לחובה, queryBy לאופציונלי, findBy לאסינכרוני. בחירת הלא-נכונה תגרום לבדיקה שנכשלת מסיבות לא נכונות.

שאלה 4.2 · למה צריך jest.clearAllMocks() ב-beforeEach?

  • א. כדי למחוק את כל קבצי ה-mock מן הפרויקט: הנחה שגויה, אף שהפקודה מאפסת את היסטוריית הקריאות של ה-mock בין בדיקות ואינה מוחקת קבצים כלשהם כלל
  • ב. מכיוון שפונקציית ה-mock מצברת קריאות בין בדיקות, ולכן clearAllMocks מאפס את היסטוריית הקריאות לפני כל בדיקה כדי שלא ישפיעו זו על זו כאן
  • ג. מכיוון ש-Jest אינו מאפשר יותר מ-10 בדיקות: הנחה שגויה, אף שאין הגבלה כזו והפקודה מאפסת את היסטוריית הקריאות של ה-mock לפני כל בדיקה בנפרד
  • ד. כדי לנקות את ה-localStorage לפני כל בדיקה: הנחה שגויה, אף שהפקודה מאפסת את היסטוריית הקריאות של ה-mock ואינה נוגעת ל-localStorage כלל בפועל
תשובה: ב

✓ תשובה: ב) — אחרי בדיקה א' שה-mock נקרא פעם אחת, בדיקה ב' תראה שהmock נקרא פעמיים (א'+ב'). clearAllMocks() מאפס את הספירה לפני כל בדיקה.

שאלה 5.1 · הפרויקט שלך מחזיר אובייקט ריק מהשרת. מה הצעד הראשון ב-Debugging?

  • א. למחוק ולכתוב את הקוד מחדש מן ההתחלה: הנחה שגויה, אף שהצעד הראשון הוא בדיקת הבקשה והתשובה בלשונית ה-Network כדי לאתר את נקודת הכשל המדויקת
  • ב. להוסיף console.log בכל שורת קוד בפרויקט: הנחה שגויה, אף שהצעד הראשון היעיל הוא בדיקת הבקשה והתשובה בלשונית ה-Network ולא הוספת לוג בכל שורה
  • ג. לשאול את מנוע החיפוש גוגל על הבעיה: הנחה שגויה, אף שהצעד הראשון הוא בדיקת הבקשה והתשובה בלשונית ה-Network לאיתור מדויק של נקודת הכשל בפועל
  • ד. לפתוח את כלי הפיתוח ואת לשונית ה-Network ולבדוק את הבקשה והתשובה האמיתיות, שכן כך מאתרים אם הכשל ב-Frontend או ב-Backend או ב-DB בפועל
תשובה: ד

✓ תשובה: ד) — Network Tab מראה בדיוק מה נשלח ומה התקבל. כך מאתרים את נקודת הכשל: האם הבעיה ב-Frontend (בקשה שגויה), ב-Backend (שגיאת שרת), או ב-DB (נתונים ריקים).

שאלה 6.1 · מדוע צריך ליצור Store חדש לכל בדיקה ב-renderWithProviders?

  • א. מכיוון ש-Redux אינו מאפשר שימוש חוזר ב-Store: הנחה שגויה, אף שהוא מאפשר והסיבה היא מניעת זיהום בין בדיקות שכן Store משותף שומר נתונים ישנים
  • ב. מכיוון ש-preloadedState מוחק את ה-Store הישן: הנחה שגויה, אף שהסיבה היא מניעת זיהום בין בדיקות שכן Store משותף שומר נתונים מבדיקה קודמת בפועל
  • ג. מטעמי ביצועים שכן Store חדש מהיר יותר: הנחה שגויה, אף שהסיבה היא מניעת זיהום בין בדיקות שכן Store משותף שומר נתונים מבדיקה קודמת ולא מהירות
  • ד. כדי למנוע זיהום בין בדיקות, שכן Store משותף יכיל נתונים מבדיקה קודמת שישפיעו על הבאה, ולכן Store חדש לכל בדיקה מבטיח בידוד מלא וידוע כאן
תשובה: ד

✓ תשובה: ד) — אם Store משותף, בדיקה א' שמוסיפה Todo תשאיר אותו לבדיקה ב'. כל בדיקה חייבת להתחיל עם State נקי וידוע. Store חדש לכל בדיקה מבטיח בידוד מלא.