TDD – תכנות מונחה מבחנים (Part-One)

7 באוגוסט 2007


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


כדי להתחיל לעשות סדר בבלאגן, נתחיל מלומר מה זה לא TDD, ונמשיך בהצגה בסיסית של TDD.


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


פרולוג


בהתחלה, כשמרחרחים במקומות הלא נכונים, מוצאים כמה דברים בנוגע ל- TDD, שכולם נכונים (חלקית), אבל לא ממש מסבירים את הנושא :



  • TDD זה סוג של Agile.
  • TDD זה Unit Testing.
  • TDD זה תקורה של קוד. (הערה: תקורה – Overhead)

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


מה זה Agile ? (בקצרה, נורא)


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


כדי לספק את סקרנות הקורא המתעניין, הרעיון מאחורי Agile (מתוך אתר agilemanifesto.org) מובע כך:


Individuals and interactions over processes and tools.
Working software over comprehensive documentation.
Customer collaboration over contract negotiation.
Responding to change over following a plan.


אני מאוד ממליץ על הספר הבא בתור ספר לימוד ל- Agile, פשוט כי הוא נהדר :



מעבר לכך, TDD זה לא Agile, אלא כלי שמשתמשים בו ב- Agile – ועל Agile Development אולי אפתח פוסט בהמשך.


אנקדוטה קטנה: בעברית, לפי מה ששמעתי שכולם אומרים לא להגיד, זה זמיש – שילוב בין זמין לגמיש. מי חשב על זה ?!? ואם כולם אומרים לא להגיד את זה, אז למה אין הצעה יותר טובה בעברית ?


מה זה Unit Testing ?


ראשית, נגדיר ש- Test זהו קטע קוד שבודק קטע קוד אחר, ויכולים להיות לו 2 תוצאות אפשריות : הצלחה, או כישלון.


למטרת הדיון שלנו, נניח כי הבדיקות מתבצעות בצורה אוטומטית (במחשב) ולא ע"י מישהו (המסכן של צוות ה-QA).


Unit Tests ("בדיקות יחידה") זוהי קבוצה של מבחנים שמטרתם המשותפת היא לבחון רכיב תוכנה אחד (ויחיד), קטן ככל שניתן – אז נאמר שרכיב התוכנה הוא היחידה  הנבדקת, ובעולם מושלם, הצלחה של כל המבחנים בקבוצה מוכיחה שהוא מתפקד כראוי (בעולם הממשי – אולי הוא מתפקד סבבה, ואולי לא כתבנו מבחן שיעלה על זה שהקוד לא סבבה בכלל). אלה המבחנים שנכתוב הכי הרבה ב- TDD, ולכן שווה שנתעכב מעט על הרעיון, על היתרונות שלו ועל הבעיות שלו.


הרעיון הוא פשוט: אחרי כל כתיבה של Class, כותבים מבחנים, שמראים שהקוד עובד כראוי.


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


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


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



  1. לפעמים, יש בעיה בשילוב בין רכיבים (מנטוס-ודיאט-קולה) למרות שכל הרכיבים עובדים כצפוי.
  2. Unit Testing לא עוזר במניעה של בעיות ביצועים.
  3. יש סיכוי סביר בהחלט שפישלנו, ופספסנו מבחן – ואז, פספסנו באג.
  4. יש סיכוי אפשרי שפישלנו, כתבנו קוד לא נכון, ומבחן לא נכון שמאשר אותו. Sad But True.

אלה דברים שאפשר להתגבר עליהם. זה לא נורא כשלא מוצאים באג – אמנם הלקוח לא הכי מרוצה, אבל עדיף לא למצוא באג אחד, מאשר לא למצוא עשרה – ולכן אנחנו אוהבים Unit Test – בחודש הראשון. בחודש השני, אנחנו מגלים שאין לנו כבר כוח לכתוב מבחנים לקוד, זה מתיש, וזה עוד קוד שצריך לכתוב סתם (שנאמר, תקורת-קוד).


כאן בדיוק משתלב הרעיון של TDD.


Test Driven Development


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


כי ה-Unit Tests נכתבו אחרי שכתבנו את הקוד שלנו. ומה אם נכתוב את ה- Unit Tests לפני שנכתוב את הקוד ? נקבל TDD. הרעיון מאחורי TDD הוא, שאם אנחנו כותבים מבחנים לקוד, אז כבר ננצל אותם כדי לבנות את הבסיס לקוד שלנו, ובכך, נתרכז רק בקוד שצריך לכתוב עכשיו, בלי לחשוב יותר מדיי על מה יהיה אח"כ. נציג כאן את העקרונות הבסיסיים, בלי אף דוגמת קוד. הפוסטים הבאים יהיו מלאים בקוד, דוגמאות, וכאלה (ברגע שאני אתחיל לכתוב אותם).


השיטה אומרת: אדום-ירוק-ניקיון (או Red-Green-Refactor). אלה השלבים:



  1. כתיבת המבחנים – ראשית, כותבים את המבחנים. כך אפשר להשיג תמונה ברורה של "איך אנחנו רוצים שישתמשו בקוד שלנו".
  2. קימפול המבחנים.
  3. הרצת המבחנים. המטרה בהרצה הזו: לראות שהקוד נופל, ונופל בצורה משמעותית (Meaninful Fail).
  4. כתיבת הקוד המינימאלי הדרוש / תיקון הקוד הדרוש (בצורה המזערית ביותר) בכדי שהמבחנים יעברו.
  5. הרצת המבחנים, במטרה לראות אותם עוברים (Pass).
  6. ביצוע פעולות Refactoring לקוד, כך שהוא קריא יותר, נוח יותר, ושמיש יותר (הכוונה כאן, היא גם לקוד של המבחנים).
  7. חזור על השלבים הקודמים לפי הצורך – ניתן לקפוץ אחורנית כמה שרוצים, אבל מתקדמים קדימה רק שלב אחד כל פעם.

נכון לא מסובך ?


הרווחנו את כל היתרונות של Unit Tests, בצורה יותר ידידותית למתכנת, כי ברגע שהמבחן כתוב, אז חצי מהקוד כבר כתוב (לפחות המבנה הכללי) ואם נבצע פעולה כלשהי שתגרום לקוד שלנו לא לעבוד, נדע מזה – לא רק לפני הלקוח, אלא גם לפני ראש הצוות. נהדר, לא ?


 כאשר הפכנו את הסדר, כתיבת ה- Unit Tests הפכה לפחות מתישה. מעבר לכך, לא השגנו כלום – כל הבעיות של Unit Tests עדיין קיימות – והן (יחד עם דוגמאות קוד) יכתבו בפרקים הבאים.


סיכום


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


האמתי, שקצת בילפתי – צריך לעבוד גם על טכניקת הכתיבה כך שהקוד בנוי ל- Unit Testing – כלומר, שהוא ניתן למבחן (Testable), והפיתרון המוצע לכך, הוא בד"כ Design Patterns, שגם עליהם, עוד ידובר בפרקים הבאים. זה לא מאוד מסובך, אבל זה לא לפרק הראשון.


בפרק הבא, נדגים את השימוש בתשתית הבדיקות NUnit, שניתנת להורדה חינם מכאן.


בנוסף, היום אין לי קוד לצרף לפוסט, אבל זה ישתנה בפרקים הבאים.


מקווה שנהנתם !

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

3 תגובות

  1. arnonrgo7 באוגוסט 2007 ב 20:03

    TDD זה לא "כתוב את המבחנים שלך" עבודה במבחנים בדידים

    כלומר כתוב מבחן הרץ את כל המבחנים, כתוב קוד שמטפל בו , הרץ את כל המבחנים, נקה את התכן

    ראה גם מאמר של Scott Ambler http://www.agiledata.org/essays/tdd.html

    ארנון

    הגב
  2. kolbis8 באוגוסט 2007 ב 9:09

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

    הגב
  3. Felix8 באוגוסט 2007 ב 14:43

    Kolbis – אני מסכים ב- 100%. זאת גם המטרה – שימוש בכל מיני "מרכיבים", "לנתק" אותם אחד מהשני, וכל הטכניקות – זה המשך הסדרה.
    ארנון – זה נכון, אבל לא בקריאה ראשונה. לו היית קורא את הפוסט הזה, בלי להכיר TDD כלל, היית אומר ש-TDD זה עבודה במבחנים בדידים ? הרגע הראינו מה זה מבחן… אבל אתה צודק, הרעיון הוא להריץ את כל המבחנים, ואם לא אמרתי את זה, אני אדגיש את זה בחלק 2.

    הגב