גיל זילברפלד
נמצא בתוכנה מאז ילדותו, כשכתב תוכניות BASIC על Sinclair ZX81 הנאמן שלו.
בעל נסיון של מעל 20 שנים בפיתוח תוכנות מסחריות וניסיון רב במתודולוגיה של תוכנה ושיטות עבודה.
כעשור מיישם עקרונות אג'ייל לפיתוח מוצרים מבדיקות אוטומטיות לבדיקות חוקרות, מנוהלי עיצוב לשיתוף פעולה בצוות, scrum ועד ל- ,Kanban ניהול מוצר מסורתי ועד lean startup ולמרות הנסיון הרב עדיין לומד מהצלחותיו וכישלונותיו.
מרצה בכנסים ינלאומיים על בדיקות יחידה ואינטגרציה TDDאג'ייל וניהול מוצרים.
הוא מחברם של "Everyday Unit Testing" כותב בלוג, מארגן של כנס Agile Practitioners ובזמנו הפנוי הוא יורה בזומבים, בשביל הכיף
https://www.linkedin.com/in/gilzilberfeld/
הבא נצלול
נתחיל בהתחלה.
API) Application Programming Interface) זהו בעצם ממשק, כמו מקלדת או שקע אתרנט. רק שזה ממשק לתוכנה – התוכנה חושפת דרך לתכנת אותה, ע"י תוכנות אחרות או מחשבים אחרים. API קיימים כמעט בכל תוכנה שהיא. גם פעולה פשוטה של לחיצה על כפתור במסך מתורגמת להפעלת API.
מחשבה מעניינת: כדי לבדוק API, אנחנו לא צריכים לחשוב כמו המשתמש. אנחנו צריכים לחשוב כמו מחשב. וגם זה כמובן לא קל.
בעולם המרושת כשאומרים API, הכוונה היא ל-REST API. אפשר לדבר הרבה על REST, הנושא עצמו מכיל מידע למאמר שלם, אבל לענייננו, REST היא ארכיטקטורה להפעלת תוכנה על הרשת. שימו לב, REST הוא לא תקן, אבל הוא כמעט ברמה הזו, מכיוון שרוב שרתי ה-REST בעולם נבנים עם כלים שכולם משתמשים בהם (לדוגמא Spring או net. core) אז אומנם הוא לא רשמי, אבל כולם מדברים אותה שפה. REST מבוסס על סטנדרטים קיימים כמו HTTP, JSON, XML, וכשאנחנו מדברים על בדיקות API, מדובר בד"כ על REST API שכולם מכירים.
מה שמעניין אותנו ב-REST, זה השימוש ב-HTTP .HTTP הוא לא רק פרוטוקול, הוא שפה עם פעלים (POST, GET), נושא (כתובת השרת) ומושאים (התוכן וה-header). אנחנו שולחים בקשה (request) שמורכבת מהחלקים האלה, ומקבלים תגובה (response). זו למעשה קריאת API.
והנה אנחנו נתקלים כבר עכשיו בסיבוכיות. אם קריאת API אחד הוא משפט שמורכב מכמה חלקים, אפשר כבר לחשוב על כל מיני צורות לבדוק אותו.
אבל סבלנות, זה רק נהיה יותר מסובך.
בקשתך בטיפול
ב-header של הבקשה מופיעים שדות שונים שיכולים להשפיע על התנהגות ה-API. לדוגמא, כשדפדפן שולח בקשת טעינה לדף HTML, למעשה זו קריאת GET API לשרת. כשאותה בקשה מגיעה מדפדפנים שונים, מתקבלת תשובה שונה (רואים את ההבדל בין מסך גדול לקטן). הדבר נעשה ע"י בניית ה-header – שדה ה-agent ממולא ע"י הדפדפן, והשרת מסתכל על מה שקיבל, ועונה בהתאם.
הדבר נכון גם לגבי APIs. אפשר לשלוח כל מיני דברים ב-header והשרת יכול להסתכל עליהם, להתייחס או להתעלם. כמו כל משרד ממשלתי. אותה בקשה ממש תענה בצורות שונות.
נניח ששולח הבקשה מצפה לתשובה ב-XML, אבל ה-API יכול להחזיר רק JSON. מה יקרה אז? תלוי בשרת.
אם אתם שואלים למה שבכלל יקרה דבר כזה, זכרו ששולחי הבקשות והשרתים שעונים להם לא בהכרח נבנים באותו מקום. הם גם לא בהכרח מתואמי גרסאות. אפשר בהחלט היום להיענות בשלילה לגבי ה-XML, ומחר להיענות בחיוב למשל כאשר גרסה חדשה עלתה לאוויר ואנו לא מודעים לכך.
בבדיקות נרצה להפעיל את אותו API עם headers שונים כדי לראות איך ההתנהגות, והאם זה עומד בציפיות שלנו.
אבל זה רק ה-header. באותה מידה נרצה לשלוח פרמטרים שונים, תוכן body שונה, אפילו מבנה path שונה ולראות מה קורה. וכמובן אפשר לייצר קומבינציות שונות ומשונות.
כבודקים נרצה לוודא שה-API שלנו עובדות ע"פ הדרישות, אבל גם נרצה להריץ תסריטי "מה יקרה אם...".
ניתן לראות שקורים דברים מעניינים בשרת. מה עוד קורה שם מאחורי הקלעים ולא מספרים לנו?
מישהו מטפל בך
אבל לא תמיד ברור מי.
להפעיל API זה בעקרון להפעיל קופסה שחורה – לא ברור לנו איך היא בנויה מבפנים. הרבה פעמים עומד איזה סדרן ובודק כרטיסים (נקרא לו Gateway Server). אם יש לבקשה את ה credentials המתאימים אפשר לעבור לשלב הבא בטיפול בה. הרבה פעמים APIs באים בהקשר authorization מסוים. יש כאלה שפתוחים לכל העולם, ויש כאלה במועדון VIP. אלה שקיבלו אישור יכנסו לקוד האמיתי.
אבל יכול להיות שסביבת הבדיקות שונה מסביבת ה-production. ויכול להיות שאפילו נרצה את השוני הזה, ואף לוותר על שירותי הסדרנות. אם אני רוצה לבדוק את ה-API פונקציונלית בלבד, אני אעדיף לייצר סביבה ללא כל הקשרי ה-security. אני מעדיף שהמערכת תגלה באגים שקשורים בלוגיקה העסקית, בבדיקות פונקציונליות. אבל בשביל זה אני צריך לדעת שאפשר לעשות את זה - צריך להבין איך המערכת בנויה והאם ניתן לפרק אותה לחלקים.
הנה עוד קסם שצריך לדעת איך הוא עובד: caching. עקרונית, קריאות GET הן cachable. בד"כ השימוש ב-caching נעשה להגברת ביצועים. זה עובד כך: פונים ל-API פעם ראשונה – תביא לי את פריט עם 1=ID.
אם פריט מס 1 מספיק מעניין, יהיו בקשות מרובות ל-API הזה עם הפרמטר הזה. במקום כל פעם לגשת ל-database עבור כל בקשה, נשמור בפעם הראשונה את התוצאה שאנחנו מחזירים בצד, וכל קריאה נוספת תחזיר את הערך השמור. חסכנו זמן, משאבי מערכת, והארכנו קצת את הזמן לאסון האקלים המתקרב.
Caching זה דבר טוב. אבל נחזור לענייננו – כשאנחנו רוצים לבדוק את ה-API, אנחנו בודקים אותו נקי או עם cache?
אנחנו לא יודעים. קופסה שחורה, זוכרים? דרך אגב, יכול להיות שה-cache מתנקה מתישהו. ויש סיכוי שנרצה לשלוט בניקוי שלו כחלק מהתסריט שאנחנו בודקים.
עכשיו הנה הפצצה: כדי שנוכל לבדוק מערכת היטב, אנחנו צריכים לדעת איך היא עובדת ואיך היא בנויה. מהארכיטקטורה שלה, לשימוש ב frameworks, ואיך הקוד כתוב. אם נמשיך להתייחס למערכות שלנו כקופסאות שחורות, ונניח ש-API תמיד עובד באותה צורה כי הוא נכתב ככה, נחמיץ תסריטים שונים בגלל ההנחות האלה.
בסדר, בסדר. לא נסבך אתכם עם דברים שיכול להיות שמחוץ לשליטתכם.
סתם. ממשיכים.
העיקר זה הסמנטיקה
האם API, מרגע היוולדו, מתנהג תמיד באותה צורה?
אף אחד לא מבטיח מה קורה בקוד של ה-API. קריאת GET נראית סטנדרטית, אבל אמרנו ש-REST אינו סטנדרטי. מכיוון שהמפתחים מתרגמים את דרישות המוצר לשפת ה-REST, הם נותנים משמעות סמנטית לקריאת ה-API, כולל אותו GET, שהיא לאו דווקא "רק" החזרת מידע.
ניקח דוגמא של Login. נניח שאפשר לבצע שלושה ניסיונות כניסה שגויים למערכת, וברביעית אנחנו ננעלים בחוץ. המפתח מתרגם את לחיצת הכפתור של הכניסה למערכת ל-POST API, עם response מתאים. כשנפעיל את ה-API 4 פעמים עם ניסיונות שגויים – אותו API עם אותם פרמטרים, נקבל התנהגות זהה בשלושת הניסיונות הראשונים ואחרת ברביעית, שם נקבל הודעת איסור כניסה מנומסת.
מכיוון שזה תסריט פשוט, הבה נתחכם קצת. כעת, מותר לחכות בין ניסויים, ואם עברו 5 דקות מאז הניסיון השגוי הקודם, ספירת שלוש הכניסות השגויות מתאפסת. זהו עדיין אותו API, עם אותם פרמטרים, אבל ההתנהגות עצמה שונה בתנאים מסוימים.
אבל אפשר לבלבל גם אחרת. אמנם ל-HTTP יש פעולת DELETE למחיקה. אבל, ניתן ללא כל בעיה ליישם מחיקה בעזרת POST. או, אם נרצה עם POST לעשות גם יצירה של פריט חדש וגם עדכון של פריט קיים. פשוט לשלוח תוכן אחר. הפרשנות ניתנה למפתחים.
כבודקים אנחנו צריכים להבין את ההקשרים השונים והמצבים השונים שבהם אפשר להפעיל APIs, איך המצבים משפיעים עליהם, ומה התגובה הצפויה. ואז צריך לייצר את המצבים האלה ולבדוק שזה אכן כך. ואז לייצר מצבים חדשים ולראות מה קורה אז.
מזל שה-כ-ל מתועד.
נו דוקו
יש דבר כזה שנקרא תיעוד API, ואפילו כלים שעוזרים. מכיוון ש-HTTP הוא כן סטנדרט, יש כלים שלוקחים את התיעוד של המפתחים שהם כותבים בתוך הקוד, והופכים אותו לספר מרהיב ודרמטי. כלים כמו Swagger שנועדו להיות ספר טלפונים (פעם היו כאלה מנייר) לכל מי שמחפש API, איך להשתמש בו ולמה לצפות.
זה מה שחיפשנו, אלו הדרישות שלפיהן נבדוק!
היכונו לאכזבה.
קודם כל, איכות התיעוד כאיכות הסקירה. אם אף אחד לא עושה review לתיעוד, אז הוא יהיה ברמת האכפתיות של המתכנת. זו כידוע מופנית לקוד, לא לקוראי קוד.
עכשיו, ניקח מתכנתת שאכפת לה. ואפילו יש לה זמן לזה. לרוב התיעוד יהיה "מה" ה-API עושה, ולא "מה המשמעות" של זה בהקשרים שונים.
חוץ מזה, תיעוד ברמת API הוא לא מספק כי API אינו אי. אין לכם API אחד, יש עשרות ומאות ואלפים. וזה הספציפי שמדובר עליו, בד"כ נקרא כחלק מתהליך. ואם ממש שפר גורלו בכמה מהם. איפה מתועד התהליך?
לזה יש פחות כלים. למעשה, אם יש לכם טסטים אוטומטיים שמריצים תהליכים (כלי הבדיקה כמו Postman וחבריו), התסריטים הם בד"כ התיעוד העדכני של דוגמאות לשימוש. אבל צריך להתחיל ממשהו.
אז נתכנן עליה לרגל למתכנתים. תהיה חגיגה. הם מסבירים מצוין. כבודקים עלינו להבין מה מתועד, מה לא, וההקשר של ה API כחלק מתסריטים שלמים. (רק לזכור לא להאכיל אותם אחרי חצות).
אוקיי, הגענו לשלב ההרצה, זה לפחות קל, נכון?
חזרה בתשובה
קל, כן.
פשוט? טוב, קודם כל בואו נשים שנייה בצד את הכלים. רובם פשוטים להבנה והפעלה.
קודם כל אנחנו צריכים לדעת על מה להסתכל. אם נסתכל על הסטטוס החוזר, זו התחלה טובה. אבל חזרה של Ok)200) בסך הכל אומרת שלא הייתה קטסטרופה. שזה נחמד, אבל זה בהחלט לא מספיק.
גם כאן יש כל מיני דברים שיכולים להוות חלק מקריטריון ההצלחה – שדות ב-header, הפעם של התגובה. מבנה התשובה. תוכן התשובה. או חלק ממנו.
נניח ש-API GET מחזיר לנו JSON נחמד. אפילו מלא במספרים, כזה שבמבט אחד אנחנו מבינים שקטסטרופה לא הייתה כאן. אבל האם מתחבא פה אסון קטן?
אז אנחנו צריכים לדעת מה המספרים אומרים. האם מספר הפריט שחזר נכון? או מומצא?
כאן נכנסת ההכנה שלנו. אם אנחנו רוצים לדעת מה קורה, צריך להכין נתונים מראש. או לדוגמא ליצור פריט בעזרת API אחר, ואז להפעיל את ה-API ולוודא שאכן חזר הנכון.
ולגבי שאר השדות? אולי צריך נתונים להשוואה, כדי שנוכל להשוות מה חזר, כי זה מה שאמור תמיד לחזור. אבל נגלה שזו לא השוואה מדויקת, כי יש חתימת זמן בתשובה בכל API, וממנה צריך להתעלם בהשוואה.
חכו שנגיע לאוטומציה של זה.
אז פשוט - זה לא.
כבודקים אנחנו צריכים לתכנן, להבין איך לתפעל את המערכת, ועל מה להסתכל, ואם יש לנו דרך להסתכל עליהם. וזה כמובן עבור כל API.
מישהו אמר אוטומציה
ודאי, את כל זה נריץ ידנית?
אין בעיה, כמו שאמרתי, הכלים יעזרו לנו, רק צריך לדעת לתכנת. שזה כמובן לא בעיה אם אתם יודעים לתכנת. אבל אחרת – כן.
נניח שחוזר JSON ענקי מה-API, ואתם יודעים שמתוך 5892 הערכים שהוא מחזיר, רק 26 מעניינים אתכם, ואתם רוצים בכוונה להתעלם בהשוואה שלהם מחתימות זמן ומקום. איך תוציאו את המידע ממנו, בעינויים? דברים כאלה הולכים רק בטוב. ובתוכנה.
הדוגמאות הפשוטות של הכלים הן אכן פשוטות, ונועדו ללמד אתכם מה אפשר לעשות איתם ואפילו למכור אותם. העולם האמיתי ידרוש הרבה יותר.
וכבודקים, אנחנו צריכים את הידע הזה. אמרנו שהלקוח של API הוא מחשב, אז צריך להתנהג בהתאם.
לכל דבר זמן ומקום
מה, יש עוד? ודאי, תודה ששאלתם.
הבנו כבר ש-API יכול להחזיר תשובות שונות על אותה שאלה. אבל זה לא רק בגלל הסמנטיקה. כמו כל הדברים בטבע, התנהגות של API תלויה בזמן ובמקום.
כל מי שאתרע מזלו אי פעם למצוא באגים של מעבר שעון קיץ/חורף (רק השבוע שמעתי סיפור על עוד אחד כזה) או 29 בפברואר יודע על מה אני מדבר. דברים שעובדים בזמן או במקום מסוים (רמז, בד"כ איפה שאנחנו נמצאים, כי הרי כל העולם עובד ככה), אבל מתגלים בעייתיים במקומות או בזמן אחר. APIs לא חסינים. או מחוסנים. Works on my machine לא פס מן העולם, הוא רק עבר לגור בענן.
והנה סט הבאגים הבאים – סביבות שונות. סביבות שונות, התקנות שונות, קונפיגורציות שונות. כל אלה יכולות להשפיע על ההתנהגות. כבודקים, נרצה להריץ בדיקות בכמה שיותר סביבות שונות. ככל שנחסוך, נדע פחות.
לפעמים אצבע אחת לא מספיקה
ללחוץ על כפתור להפעלת ה-API זה קל. אבל מה קורה כשכל עם ישראל ואחותו יפעילו את ה-API הזה באותו רגע? או מהר מאד אחד אחרי השני?
אני לא מדבר על בדיקת עומסים "רגילה", כמו פניות כלליות לשרת. אני מדבר ספציפית על ה-API שלנו. בהנחה שמישהו מטפל בעומס הרגיל, מה קורה מבחינה פונקציונלית. מי נכנס ראשון והאם מקבלים תשובה בסדר הנכון? והאם זה קורה בצורה קונסיסטנטית?
נניח שה-API שלנו צורך קצת זיכרון, שישוחרר בתהליך מסודר. מה קורה אם התהליך לא מסודר בגלל הפניות מקביליות?
כבודקים, אנחנו צריכים להבין את המשמעות של הפעלה מקבילית, ולתפעל אותה בהתאם לציפיות מהמערכת. גם כאן הבנה איך הקוד כתוב ורץ יכולה לעזור.
קבוצת תמיכה
אבל לא באתי לייאש. כמו שאומרים בפסיכולוגיה נעשה reframing, נגדיר את הבעיה מחדש.
התשובה ל "בדקת את ה-API" היא אחת מהשתיים: "לא" או "לא כמו שרציתי". אבל זה לא משנה.
מה שכן משנה זה איפה נמצאים הסיכונים במערכת. אנחנו צריכים לבדוק, לא ברמת הקוד (מה שבעצם מגדיר API) אלא ברמת השימוש. ככל שנדע איך יהיה השימוש ב-API, נוכל לתכנן את הבדיקות בצורה יותר טובה, ולתעדף תסריטים מסוימים על אחרים.
אז במקום לדבר על ה-API, צריך להתחיל ב"מה חשוב לנו". זה דורש מחשבה, זה לא אוטומטי. מכאן נגזור את התסריטים הכי מעניינים, עם הפרמטרים הנכונים, וההכנה המתאימה. ואם יש זמן, גם exploratory testing לא הזיק לאף אחד אף פעם.
כמה פשוט, ככה מסובך.