פעם בשנתיים אני וחבר לעבודה (שמואל גרשון) מעבירים קורס בדיקות תוכנה באוניברסיטה העברית. הקורס די פופולרי - יש בערך 100 סטודנטים כל מחזור. אנחנו לא משלים את עצמנו שזה בגלל שבדיקות תוכנה זה נושא שכל כך מושך את הסטודנטים. יותר הגיוני שזה אחד מהבאים:
הקורס בשעות נוחות בלו"ז הכללי
הקורס נחשב קורס קל
אנחנו מרצים כל כך נפלאים
תהיה הסיבה אשר תהיה יש לנו קהל שבוי, שאפשר לשכנע חלק ממנו שבדיקות תוכנה זה אתגר ושצריך יותר מרק דופק בשביל לעשות אותו טוב. כהדגמה, אנחנו מציגים תכונות מוכרות בתוכנה, שקשה מאוד לבדוק. למשל: "במערכת ההפעלה Windows יש אפשרות להזדהות בעזרת זיהוי פנים. איך הייתם בודקים את זה?".
התשובות הראשונות הן הטריוויאליות: מעמידים אנשים מול המצלמה ורואים אם המערכת מזהה את מי שרשאי להשתמש במחשב, ודוחה את מי שאינו רשאי. מכאן מתחיל דיון על השונות הרבה שצריך להתמודד איתה (עם או בלי זקן, משקפיים, איפור, גבות, כובע; מאפיינים אתניים; מצב תאורה; וכו' וכו' – אין סוף מצבים). אנחנו גם מסבירים שיש צורך לבדוק שוב ושוב כל גרסה שמשוחררת. בסופו של דבר מגיעים למסקנה הבלתי נמנעת שצריך אופציה להזין תמונות או וידיאו מוקלט אל תוך האלגוריתם שמזהה את הפנים. כלומר: חייבים testability feature (או testability hook) על מנת לאפשר לבדוק את התוכנה בצורה יעילה וחזרתית.
נִצְפּוּת ונִשְׁלָטוּת
אה... עברית שפה קשה. באנגלית זה הרבה יותר מוכר:
Observability and Controllability
על פי מילון האקדמיה העברית:
נִצְפּוּת: תכונה של מערכת המאפשרת לצפות מבחוץ במשתנים הפנימיים שלה ולקבל מידע עליהם.
נִשְׁלָטוּת: נכונות לקבל פקודות מגורם חיצוני ולבצען.
גם אם לא הכרתם את המונחים האלה, הם הא"ב של בדיקות: אנחנו מפעילים פקודה מבחוץ על המערכת הנבדקת, על מנת להפעיל תכונה מסוימת של המערכת הנבדקת. אנחנו בוחנים את התוצאה על מנת לוודא שהמערכת פעלה כראוי. (או בעצם מתוך תקווה שהיא לא פעלה כראוי ואז מצאנו באג) . למעשה, מערכת שאין בה נשלטות או נצפות לא יכולה להיבדק.
יש פקודות שקל מאוד לבצע: הקשה על המקלדת מוסיפה אותיות לתוך מעבד מילים. גם קל מאוד לצפות אם זה עבד או לא: מסתכלים על המסך. אבל יש תכונות שקשה להפעיל. למשל: על מנת לבדוק איך מערכת מתנהגת כאשר הרשת מאוד עמוסה צריך להריץ סימולציה של הרבה משתמשים, או להפעיל כלי כמו NetLimiter על מנת להקטין באופן מלאכותי את רוחב הפס של האפליקציה הנבדקת. לעיתים קשה לבדוק אם אכן המערכת הגיבה נכון. למשל: האם הקָשְׁחָה (firmware בלעז) ביצעה reset מהיר אם היא נתקעה ליותר מ-200 מילישניות.
כלומר, היכולת שלנו לבדוק מערכת בצורה טובה קשורה קשר הדוק לרמת הנשלטות והנצפות של כל תכונה. ומה עושים עם תכונות שלא כל כך קל לשלוט בהן או לצפות בהן? אפשרות אחת זה להחליט שאם אני, כבודק, לא יכול לגרום לתוכנה לעבור מסלול מסוים, כי קשה מאוד - עד כדי בלתי אפשרי - לארגן קלט שיגרום לזה, או שאין דרך לשים לב שמסלול זה אכן נלקח, כנראה שגם למשתמש זה לא ממש יפריע. ואם כך אפשר לקחת סיכון ופשוט לא לבדוק את זה. זה גישה סבירה במקרים מסוימים, אבל לא תמיד. במקרים אלה נאלץ להוסיף תכונות למוצר שכל ההצדקה שלהן הוא שיפור הנשלטות ו\או הנצפות של התוכנה. אלה תכונות לצורך שיפור הבדיקתיות, ולפחות אצלי בארגון זה נקרא testability hooks או בתרגום חופשי שלי: עזרי בדיקה (לא מצאתי תרגום סביר; sue me).
איך "משיגים" testability hooks?
כיוון שהלקוחות לא צריכים לבדוק את המוצר, הסיכוי שבקשה להוסיף עזרי בדיקה תבוא מהם הוא קלוש. כנ"ל מאנשי הארכיטקטורה והמוצר. העול אם כך הוא עלינו, הבודקים. האתגר הוא שככל שמתקדמים בפיתוח המוצר יותר קשה "לדחוף" תכונות חדשות ללו"ז הצפוף, ומצד שני, ככל שמתקדמים בפיתוח המוצר אנחנו מזהים תכונות שאי אפשר לבדוק אותם ביעילות, או אולי אפילו בכלל, ללא עזרי בדיקה. אכן לא פשוט, אבל לא סיבה להרים ידיים. אם תשקיעו זמן בתחילת הפרויקט להגדיר (ולכתוב!) אסטרטגיית בדיקות, תאלצו לחשוב על "איך אנחנו הולכים לבדוק את זה" ויש סיכוי לא רע שתעלו לפחות על חלק מהעזרים הנדרשים ותוכלו לבקש אותם בתחילת הפרויקט. בכלל, העקרון של Panic Early and Avoid The Rush רלוונטי מאוד כאן: ככל שתבקשו עזרי בדיקה מוקדם יותר, כשהלחץ על המפתחים נמוך יותר, יש יותר סיכוי שיקשיבו לכם. דרך אחרת – יעילה במיוחד – היא לשכנע את המפתחים שעזר בדיקות מסוים יעזור גם להם בזמן הפיתוח, וודאי בזמן דיבוג של בעיות. מניסיוני, עזרי הבדיקה שמתוחזקים לאורך זמן הם אלה שנמצאים בשימוש של המפתחים.
דוגמאות
למי שלא חווה את ההבדל בין בדיקות עם ובלי עזרי בדיקה, קשה אולי לדמיין מה לבקש. לכן, הנה מספר דוגמאות, בתקווה שיעזרו לכם לחשוב בכיוון, ויפתחו לכם את בתאבון...
1. שימוש ב – windows registry
Windows registry הוא בעצם מסד נתונים מאוד גדול שמחזיק מידע על כל צ'ופציק בתוכנות שעל המחשב שלכם (אני לא בטוח שמיקרוסופט יחתמו על התיאור הזה). אפליקציות יכולות לכתוב ולקרוא מבסיס הנתונים הזה בעזרת קריאות מערכת. ראיתי שני סוגי שימוש ברג'יסטרי:
א. שיפור הנצפות: כתיבה של נתונים תוך כדי ריצה של האפליקציה הנבדקת. למשל, אם תרצו לדעת אם פונקציה מסוימת הופעלה, אפשר להוסיף לה שורת קוד שתכתוב משהו לתוך הרג'יסטרי.
ב. שיפור הנשלטות: הוספת קוד למערכת שקורא ערך של משתנה ברג'יסטרי. בתלות בערך, התוכנה מבצעת משהו (או מדלגת על משהו).
אפשר לכתוב ולקרוא את הרג'יסטרי ידנית או מתוך קוד אוטומציה, ובצורה כזאת לייצר מקרי בדיקה שלא ניתנים לביצוע ישירות דרך הקלט הקיים.
2. השיטה הבאה משמעותית במיוחד כשבודקים תוכנה במערכת משובצת מחשב (embedded). מערכות אלה מוגבלות ביכולת להחצין את המצב הפנימי של המערכת, דבר שמגביל מאוד את הנצפות שלהן. גם במערכות משובצות שכן מאפשרות לשלוח הודעות החוצה (אל מערכת הבדיקה), שליחת הודעות אלו מעמיסות את המעבד ומערכת הזיכרון של המערכת הנבדקת כי צריך לעבור כמה שכבות מרמת ה-embedded דרך הדרייברים, ועד למערכת ההפעלה שעליה רצה תוכנת הבדיקה. שליחת הודעות כאלה משפיעה על התנהגות התוכנה הנבדקת (דוגמה קלאסית ל"אפקט הגשושית" – probe effect) ומכאן עלולה להשפיע על תוצאות הבדיקות. הפתרון הוא הכנסת עזר בדיקות. במקום לשלוח הודעות החוצה, מכניסים שורות לתוכנה ששומרות קוד קצר שמתאר את מצב המערכת ישירות לזיכרון הדינמי, הפנימי, של המערכת. קודים אלה לוקחים מעט מאוד זיכרון והתקורה של כתיבה לזיכרון הדינמי היא מזערית. את הנתונים שנאספים קוראים רק בסוף הרצת הבדיקה, כך שפעולה זו לא משפיעה על התוצאה.
3. הוספת APIs שמאפשרים לעשות פעולות שלא נדרשות במערכת הסופית אבל כן עוזרות מאוד בבדיקות. למשל:
א. "דחיפה" של ערך גבוה למונה של אירועים מסוימים, על מנת לראות מה ההתנהגות כשמגיעים למספר הגבוה ביותר שהמשתנה של המונה יכול לשמור (למשל: ~2 גיגה, במשתנה מסוג integer32).
ב. מחיקה של נתונים שבשימוש נורמלי אסור למחוק (מסיבה רגולטורית, למשל). כך נוכל להריץ בדיקה אוטומטית שכותבת משהו חדש לבסיס הנתונים, מוודאת שהמשהו אכן נכתב, ואז – בעזרת API שנכתב רק עבור צרכי הבדיקות - מוחקת אותו. זה יאפשר להריץ את הבדיקה הזאת שוב בסבב הבדיקות הבא.
4. Chicken Bits
בסלנג אנגלי chicken משמעותו פחדן. יש מקרים שבהם אנחנו מצד אחד מאוד רוצים לשחרר תכונה מסוימת, ומצד שני מפחדים שהיא הולכת לדפוק את כל העסק. הדילמה עוד יותר גדולה כשמדובר בתכונה שממומשת בחומרה של מעבדים, כי תכונה טובה יכולה לגרום להבדל גדול במכירות, אבל אם היא לא תעבוד, ייקח כמה חודשים בקו הייצור עד שיגיעו מעבדים שהתכונה הוסרה מהם. יצרני מעבדים עושים לכן שימוש בשיטה זו על מנת לאפשר הסרה של תכונות ברמת סיכון גבוהה. התכונות משוחררות עם המוצר ובמקביל מכניסים לממשק התוכנה-חומרה ביט שכאשר הוא עם ערך 1 התכונה פועלת, ואם הוא עם ערך 0, התכונה מבוטלת. מוסיפים יכולת לשלוט על ערך הביט מבחוץ (נשלטות!) וקיבלנו את האפשרות to chicken out (להבהל? להתחרט?) ולבטל את התכונה.
אפשר להשתמש ברעיון דומה כעזר בדיקות. תארו לעצמכם מערכת משובצת מחשב שצריכה להפעיל את המאוורר הפנימי שלה כאשר אחת משלוש התופעות הבאות קורות: כשאתם מורידים מהרשת נתונים בקצב מעל 120 מגה בייט לשנייה (ממוצע בדקה האחרונה); כאשר חום המעבד עולה מעל 85 מעלות (אפילו לרגע) וכאשר אחוזי השימוש במעבד עולים מעל 90 אחוזים בממוצע על עשר השניות האחרונות. כיוון שהורדת נתונים מהרשת מעלה את חום המעבד ואת אחוזי השימוש במעבד, קשה לדעת מה "הקפיץ" את ההחלטה להפעיל את המאוורר. היה נוח אם אפשר היה לאפשר לשלוט איזה תנאי פעיל בכל רגע, ואז לייצר מקרה בדיקה שמכוון כך שנעבור את הסף של התנאי היחיד שפעיל. יישום chicken bits שכל אחת מהן תבטל את אחד מהתנאים להפעלת המאוורר תאפשר בדיקה קלה יותר של התכונה. לא חייבים שה"ביטים" האלה יהיו בחומרה; הם יכולים, למשל, להיות משתנים ברג'יסטרי (עיין ערך).
עזרי בדיקה בוורסיה הרשמית
מלכתחילה, סביר שעזרי הבדיקה ימומשו רק בוורסיות של המוצר שמשמשות לבדיקה, וימחקו מהקוד בוורסיה שנוציא למשתמשים. הרי אין למשתמשים צורך ביכולות האלה ולפעמים זה גם חושף מידע פנימי. מצד שני, יש עזרי בדיקה שיכולים לעזור לדבג את המוצר בשטח, או להפעיל תכונה שמורידה את הביצועים, אבל נדרשת עבור חלק מהמשתמשים. עוד סיבה טובה להשאיר את העזרים בתוך הקוד המבצעי הוא שזה יבטיח שהם יעודכנו כחלק מעדכוני התוכנה, דבר שיעזור בבדיקות התחזוקה. ההחלטה יכולה להיות לגבי כל עזר בדיקות בנפרד. אפשרויות:
עזר הבדיקה קיים רק בוורסיות פנימיות
עזר הבדיקה קיים במוצר הסופי, אבל ללא דוקומנטציה (אם כי סביר שמישהו יגלה את זה)
עזר הבדיקה קיים במוצר הסופי והופך להיות חלק מהמוצר (כלומר: מופיע בתיעוד של המוצר)
מי ישמור על השומרים
ולא לשכוח: אנחנו מסתמכים על עזרי הבדיקה בשעת בדיקת המוצר. מי מבטיח לנו שהם עובדים? ככלות הכל, הם ממומשים בתוכנה, ומי כמונו יודע שאי אפשר לסמוך על תוכנה עד שבודקים אותה… לכן: לפני שמשתמשים בעזרי הבדיקה צריך לבדוק את העזרים עצמם. במקרים קשים אפשר לעשות זאת מידי פעם בעזרת בדיקות קופסה לבנה (עם debugger, למשל), אבל אם אפשר, עדיף לוודא שהעזרים עובדים כחלק מכל סבב בדיקות.
תרגול נוסף
מצגת שלי על הנושא, עם עוד הרבה דוגמאות ומושגים: Testability