[ הרצאה · Riverside Live 2024 ]

שיטות עבודה מתקדמות לבדיקת API

וידאו · 18 באוקטובר 2024 · 44:00 · יוני גולדברג

בדיקות API בין מיקרו־שירותים דורשות יותר מטסט שעובר מקומית. יוני גולדברג מציג מוקים, OpenAPI, חוזים ו־fakes שיעזרו לכם לתפוס כשלים לפני פרודקשן.

צפו בהרצאה · 44:00
תמונה ממוזערת של ההרצאה: שיטות עבודה מתקדמות לבדיקת API
[ כתבת ההרצאה ]

הסיפור שמאחורי ההרצאה

[ תקציר מהיר ]
  • בדיקות API צריכות לכסות את המרווח המסוכן בין שירותים, לא רק כל רכיב בנפרד.
  • Network Mocking מדמה תשובות ותקלות רשת בלי לוותר על בדיקת הקוד ששולח בקשות.
  • בדיקות End-to-End מוגבלות בעיקר ל-happy path ולא מאפשרות לשלוט במצבי כשל פנימיים.
  • OpenAPI client מוסיף שכבת הגנה סטטית מול חוזה השירות החיצוני.
  • Mock server ו-fake stateful מאפשרים בדיקות ריאליסטיות למצבי כאוס ולמצבים אסינכרוניים.

בדיקות API בין מיקרו שירותים לא נופלות כי מישהו שכח assert. הן נופלות כי שני שירותים בטוחים שהם מדברים באותה שפה, ואז פרודקשן מגלה שלא. יוני גולדברג פותח בסיפור על פלטפורמת כרטיסים: שירות ההזמנות פנה לשירות משתמשים, קיבל כשל שאמור היה להפעיל מסלול פיצוי, ובהמשך גם 503 שאמור היה להפעיל retry. שני מנגנוני ההצלה היו בקוד. גם טסטים היו. ובכל זאת, ביום המכירה הגדול המערכת הפסיקה למכור כרטיסים. זו בדיוק הבעיה בבדיקות API בין מיקרו שירותים: אפשר לבדוק את הלוגיקה שלנו היטב ועדיין לפספס את מה שקורה בקו שבין שני הרכיבים.

למה בדיקות API בין מיקרו שירותים נכשלות בפרודקשן

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

הלקח של יוני חד: הקוד והטסט שנכתבים על ידי אותו אדם יכולים לשתף אותה הנחה שגויה. בתוך כל קומפוננטה יש לנו לינטרים, unit tests, visual regression ועוד שכבות הגנה. בין השירותים יש מרחב אחר לגמרי: token שפג, schema שהשתנתה, socket שהתנתק, timeout, סטטוס שלא פירשנו נכון. אם הבדיקה רק מאשרת את התרחיש שציפינו לו, היא לא בהכרח בודקת את החוזה מול העולם שמחוץ לתהליך שלנו.

איך בודקים API בין שירותים בלי להסתמך על happy path

נקודת פתיחה גרועה היא mock בזיכרון שמחליף פונקציה בקוד. הוא עשוי להוכיח שהלוגיקה אחרי קבלת תשובה עובדת, אבל הוא עוקף את הקוד שבאמת בונה ושולח בקשת HTTP. במקום זה יוני מציע network mocking: לוכדים את הבקשה בשכבת הרשת ומחזירים תשובה כאילו הגיעה מהשירות השני. כלים כמו MSW, Playwright ו-Nock עושים זאת בדרכים שונות, אבל הרעיון זהה - הקוד של הקונסיומר ממש פונה ל-URL, והטסט שולט במה שיחזור ממנו.

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

יוני גם מזהיר מ-end-to-end כפתרון אוטומטי. בדיקת end-to-end מול שירות אמיתי תראה לרוב את מה שהשירות בוחר להחזיר כרגע, בדרך כלל happy path. אין לה ממשק שיגרום לו להחזיר 503 או להעביר אותו למצב פנימי של תקלה. לכן היא מתקשה להגיע למצבי הכשל שחשוב לנו להבין.

Contract testing למיקרו שירותים: למה Pact אינו תמיד הבחירה הנכונה

השם שעולה כמעט מיד כשמדברים על contract testing למיקרו שירותים הוא Pact. במודל המסורתי שלו, הקונסיומר מגדיר מסמך ציפיות: לאיזה route הוא פונה ואיזו תשובה הוא מצפה לקבל. המסמך נשמר במקום משותף, וה-provider מריץ אותו מול ה-API שלו ומדווח אם הוא עומד בציפיות. זה נשמע כמו חוזה, אבל יוני מצביע על בעיה מהותית: צד אחד מגדיר לשני איך הוא אמור להתנהג.

זה יוצר חיכוך ארגוני ותחזוקתי. הקונסיומר צריך לתאר מספיק תרחישים כדי להגן על עצמו, ה-provider צריך לשמור תאימות לציפיות רבות, והחוזה עלול להפוך לשכבה נוספת שאינה מקור האמת של ה-API. בנוסף, הדרך הזאת אינה פותרת את השאלה איך בודקים state מורכב או כשלים ריאליסטיים. יוני מתאר את הכיוון העדכני של Pact כהתכנסות לסטנדרט OpenAPI ולבדיקות דו-כיווניות. המסר המעשי אינו שצריך למחוק כלי, אלא שצריך להעדיף חוזה משותף וברור על פני ניחושים של הקונסיומר.

מתי network mocking נותן כיסוי טוב יותר מ-mock בקוד

network mock טוב מאפשר לשלוט לא רק בתשובה אלא גם ברצף: שגיאה בפעם הראשונה, הצלחה בפעם השנייה, payload שונה בכל ניסיון. זה מתאים במיוחד לזרימות שבהן הקונסיומר צריך להגיב לסטטוס, header, token או צורת response מסוימת. במילים של יוני, המטרה היא לא לבחור ספרייה אחת קדושה. המטרה היא לוודא שהבקשה באמת יוצאת דרך שכבת הרשת, ושאפשר לדמות את מה שהשירות החיצוני עלול לעשות.

אבל network mocking לבדו עדיין עובד על סמך הגדרה שאנחנו כתבנו. הוא לא יודע מעצמו אם ה-stub שלנו כבר לא מתאים לשירות. לכן כדאי להתחיל מחוזה שמגיע מה-provider. יוני ממליץ שלפחות תהיה שכבת typing או client שנוצר ממסמך API. OpenAPI הוא ה-default הטוב לאינטגרציה טיפוסית: הוא מפחית טעויות בסיסיות עוד לפני ריצה, ומכריח את שני הצדדים לדבר על אותו schema.

איך בודקים timeout, retry ו-chaos בלי להאט את הסוויטה

כשלי רשת אינם תרחיש אקזוטי. בדוגמה של יוני, השירות השני ירד לארבע דקות, והקונסיומר היה אמור לבצע backoff ו-retry. בדיקה טובה מגדירה timeout של הקליינט במקום אחד, כך שכל קוד היציאה החוצה משתמש בו. אחר כך ה-mock מחזיר בפעם הראשונה עיכוב גדול מה-timeout, ובפעם השנייה תשובה מוצלחת. ה-assert צריך להוכיח שהבקשה הראשונה נכשלה בזמן ושניסיון חוזר הוביל להצלחה.

אין צורך לחכות באמת. ספריות mocking שיודעות שה-delay ארוך מה-timeout יכולות להפיל את הבקשה מיד, ובמקרים אחרים אפשר לשלוט בשעון. כך בודקים timeout ו-retry ב-API בזמן דומה ל-unit test, במקום להפוך את הסוויטה למכונת קפה. זה controlled chaos: מייצרים socket שמתנתק, delay או תגובת כשל באופן נשלט, מהיר וחוזר על עצמו.

Fake API לבדיקות אינטגרציה: כיסוי של state אמיתי

יש סוג כשל שמוק רגיל לא חושף: state שלא נוצר מיד. נניח שמוסיפים כרטיס דרך API ואז מבקשים מיד את הכרטיסים. במערכת אמיתית ההוספה יכולה לשים הודעה בתור, להפעיל cron job או לפנות לשירות נוסף. בקשת ה-GET הבאה יכולה להחזיר מערך ריק, גם אם ה-POST עצמו הצליח. בדיקת UI יכולה לעבור אם המוק מחזיר מיד את מה שהגדרנו, ואז להיכשל ב-QA או בפרודקשן.

כאן נכנס fake API. ה-provider בונה גרסה פשוטה של עצמו עם אותם routes, לעיתים כספרייה שמרימה שרת מקומי ושומרת state בזיכרון. בדוגמה של יוני, כרטיס חדש נכנס קודם ל-pending tickets, ורק פעולה ייעודית מעבירה אותו ל-approved tickets. ה-fake נותן לקונסיומר מציאות יותר אמינה, וגם backdoors לבדיקות: אפשר להכניס את השירות למצב מסוים ולבדוק מה הקונסיומר עושה.

המחיר הוא תחזוקה של שני מימושים. ההגנה היא להריץ את הבדיקות שמוגדרות עבור ה-API האמיתי גם מול ה-fake. אם שתיהן עוברות, יש לנו לפחות עדות לכך שהם מתנהגים אותו דבר כלפי חוץ. יוני מזכיר גם שימוש ב-port 0 כדי לקבל פורט אקראי, וכך להריץ בדיקות במקביל בלי התנגשויות.

למי ההרצאה הזאת

ההרצאה מתאימה למפתחי backend ו-frontend שעובדים מול שירותים אחרים, לאנשי QA שמנסים לבנות כיסוי מעבר ל-happy path, ולמי שאחראי על מערכת שה-API שלה כבר לא מסתיים באפליקציה אחת. תצאו ממנה עם דרך לחשוב על שכבות: client שנוצר מחוזה, network mocks לכשלים נשלטים, mock server לוולידציה בזמן ריצה, ו-fake כשה-state עצמו הוא חלק מהסיכון.

השורה התחתונה

  • טסט שעובר לא מוכיח שהאינטגרציה תשרוד שינוי חוזה.
  • אל תחליפו את בקשת הרשת בפונקציה בזיכרון כשמה שחשוב הוא הקוד ששולח HTTP.
  • OpenAPI או חוזה typed אחר הוא קו הגנה בסיסי, לא תוספת נחמדה.
  • בדקו timeout ו-retry בתרחיש אמיתי, אבל עם זמן מדומה או mocking מהיר.
  • כש-state אסינכרוני משנה את התוצאה, fake עדיף על mock סטטי.
  • הכלי הנכון תלוי בסיכון של האינטגרציה, לא בשם הכי פופולרי בגוגל.

שאלות נפוצות

השאלות והתשובות המלאות מופיעות בהמשך העמוד.

הדרך לבנות אמינות בין שירותים אינה למצוא טסט אחד גדול שמריץ הכול. היא לבחור את שכבת הבדיקה לפי הסיכון: חוזה עבור schema, mocking עבור תגובות חריגות, ו-fake עבור state. לצפייה בהרצאה המלאה של יוני גולדברג: https://www.youtube.com/watch?v=FsfXeAFjKS8.

"טסטים אמורים להיכשל, זה הקטע, הם הדבר שאני רוצה שייכשל לפני פרודקשן."

- יוני גולדברג

"עכשיו לראשונה, יש כאן שני שחקנים במשחק, לא רק אני."

- יוני גולדברג

"קודם כל אני חושב ש-Open API Client זה צריך להיות הסטנדרט החדש."

- יוני גולדברג
[ פרקי ההרצאה ]
  1. 00:00:38
    כשל במכירת כרטיסים
    סיפור על השקת פסטיבל שנפלה בפרודקשן למרות מנגנוני פיצוי ו-retry שנכתבו מראש.
  2. 00:07:08
    Network Mocking בפועל
    איך ליירט בקשות רשת ולדמות תשובות שירות בלי להחליף את קוד הלקוח במוק בזיכרון.
  3. 00:12:03
    מגבלות בדיקות End-to-End
    למה בדיקות מקצה לקצה מובילות בעיקר ל-happy path ולא חושפות מצבי כשל נשלטים.
  4. 00:14:13
    הבעיה עם Pact
    יוני מפרק את זרימת Contract Testing של Pact ומסביר למה ברוב המקרים אינו ממליץ עליה.
  5. 00:17:43
    OpenAPI client סטטי
    יצירת client וטייפים מ-OpenAPI מגינה על הקונסיומר מפני נתיבים, פרמטרים וסטטוסים שגויים.
  6. 00:23:18
    Chaos Testing נשלט
    דימוי timeout ו-retry באמצעות מוק מאפשר לבחון עמידות לתקלות רשת במהירות.
  7. 00:28:45
    Mock server מאמת חוזה
    WireMock יכול לאמת בזמן ריצה שהבקשה תואמת ל-OpenAPI ולהפיל טסט על הפרת פורמט.
  8. 00:33:10
    בעיית ה-state הלא צפוי
    מערכות אסינכרוניות עשויות להחזיר מצב שעדיין לא הושלם, גם כשהבדיקות המקומיות עוברות.
  9. 00:35:13
    Fake stateful לשירות
    שירות fake עם state בזיכרון ו-backdoors נותן לקונסיומר שליטה במצבים ריאליסטיים לבדיקה.
  10. 00:42:43
    ברירת המחדל המומלצת
    OpenAPI client הוא שכבת הבסיס, ומעליה בוחרים mock server או fake לאינטגרציות מורכבות וקריטיות.
[ נושאים ]

מה כוסה בהרצאה

#בקאנד וענן #מתודולוגיה #Backend #API #Testing
[ שאלות נפוצות ]

שאלות מההרצאה

איך בודקים API בין מיקרו שירותים?

מתחילים בחוזה משותף, למשל OpenAPI, שממנו אפשר ליצור client או שכבת typing. מעליו בודקים את הקונסיומר מול network mock שתופס את בקשת ה-HTTP ומחזיר תגובות נשלטות. כך בודקים גם את צורת הבקשה שיוצאת, גם תגובות שגיאה, וגם את ההתנהגות של הקונסיומר לאחריהן.

מתי כדאי להשתמש ב-mock API בבדיקות אוטומטיות?

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

מה ההבדל בין contract testing ל-end-to-end testing?

contract testing בודק שהקונסיומר וה-provider עומדים בחוזה מוסכם של request ו-response. בדיקת end-to-end מפעילה רכיבים אמיתיים יחד, אך לרוב נשארת ב-happy path כי אין דרך פשוטה לכפות על שירות אמיתי להחזיר 503 או להיכנס למצב פנימי חריג.

איך בודקים timeout ו-retry באינטגרציית API?

מגדירים timeout ידוע לקליינט, ואז מגדירים ב-network mock שהתגובה הראשונה תתעכב יותר ממנו והתגובה השנייה תצליח. הבדיקה צריכה לוודא שהכשל הראשון לא עצר את הזרימה ושה-retry הצליח. אין צורך להמתין בפועל: ספריות mocking או שליטה בשעון מאפשרות לקצר את התרחיש למילישניות.

[ קהילה ]

רוצה לדבר על ההרצאה?

הצטרף לקהילת המפתחים שלנו ב-WhatsApp.