צעדים ראשונים בתכנות מבוסס בדיקות

לאחרונה התחלתי ללמוד על פיתוח מבוסס בדיקות. מה הכוונה בפיתוח מבוסס בדיקות ?

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

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

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

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

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

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

11 מחשבות על “צעדים ראשונים בתכנות מבוסס בדיקות

  1. rotema8

    זאת לא תיאוריה וזה חוסך הרבה מאוד כסף וזמן במציאות . לא כל כך ברור לי על מה בדיוק אתה משקיע מחשבה כל כך גדולה ולמה קשה כל כך לממש אותה ?
    קח למשל מסמך איפיון כמו זה :
    https://idkn.wordpress.com/2009/04/27/what-is-a-software-specification/

    האם אתה יכול לעשות לו Review ? (כן)
    האם אתה יכול לפתח מסמכי בדיקות לפי האיפיון ? (כן)
    הם אתה מסוגל עכשיו לכתוב קוד שמקיף את כל המצבים האפשריים ? (כן)
    הם אתה מסוגל לממש (להריץ) את הבדיקות ? (כן)
    האם אתה מסוגל להוכיח שהקוד עומד בתנאי האיכות שהצבת במסמכי הבדיקות ? (כן)
    יש לך מוצר שלם ….

  2. ארתיום

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

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

    בנוסף, יש עוד בעיה רצינית — testability (מונח בעברית?). כיצד לעשות בדיקות עבור למשל, ממשק web? זה כבר חצי סיוט, כיצד לעשות טסט לרכיב פרוטוקול, נגיד server — צריך לממש עבורו client כדי לעשות דבר כזה…

    בקיצור בתיאוריה זה מאוד יפה… במציאות זה הרבה יותר מסובך אם כי עדיין מאוד מומלץ…

  3. ik_5 מאת

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

  4. rotema8

    האם אחרי שתכתוב את הקוד זה לא יהיה מייגע לכתוב unit test ? כנראה שכן אבל אחרי שכתבת את הקוד כבר אז יש לך עבודה כפולה : עכשיו אתה צריך לחזור לתקן את הקוד ולבדוק שוב את התיקון (בשגיאה שאולי היית יכול לחסוך מלחתחילה)

    testabilty =בר בדיקות או בדיק. מה הבעיה לעשות בדיקות עבור client-server ?
    תזכור שכל הבדיקות מבוססות על אותו רעיון : expected =actual . אם אתה יודע מה אתה מכניס לאותו רכיב ומה הוא אמור להחזיר לך אז לא צריכה להיות בעיה .
    ברור שאתה צריך לממש בסופו של דבר את כל הרכיבים על מנת להריץ את הבדיקות אבל אנחנו מדברים על שלב הרבה יותר קודם , עוד בשלב התיכנון .

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

    זה לא מסובך יותר , זה פשוט יותר . עניין של צורת מחשבה .

  5. Boris Shtrasman

    לקחת משהוא מהשיחה שלנו אני רואה הא ?

    בנושא האקדמיה צריך קצת להרחיב – כאשר אתה מפתח קוד אתה לא רק כותב לוקח חלקים מוכנים ומדביק אותם (ראה ערך מפתחי copy-paste) אלה אתה גם מייצר אלגוריתמים ופה בדיוק ניכנסת האקדמיה לתמונה .

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

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

    אבל האם הבדיקה שלך בודקת שהאלגוריתם באמת עושה את משהוא אמור לעשות ?

    ניקח לדוגמה מיונים האם אתה תעיז לממש QuickSort בכל מצב ? הרי ישנם מצבים שעדיף להשתמש ב mergesort (מהסיבה הפשוטה שהוא תמיד אבל תמיד באותה סיבוכיות).

    האם לחיפוש הדרך הקצרה ביותר לעבור על כל הנקודות (אתר ווב שממליץ על מסלול טיול לדוגמה / פריסת רשתות וכן הלא ) תבחר Prim או Kruskal ? או אולי עדיף בכלל את Chazelle .

    ומה לגבי לחפש את הדרך הקצרה ביותר ?
    אמנם הדברים האלה נשמעים אקדמים ומשעממים אבל יש להם יעוד במציאות (בן עם באתרי ווב , לשרתי SIP ולעוד מיליון מימושים אחרים).

    אני מכיר (וקראתי ) על מעט מאוד אנשי תוכנה שיש להם היכולת לפתח קוד שהוא יהיה יעיל ומתאים ללא הידע המתמטי הדרוש .

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

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

    כמו שכבר אמרו תמיד אבל תמיד צריך לסגור על סוג של SRS (ומניסיון מר לא להתחיל לפתח עד שזה לא סגור 100% כי תמיד ללקוח יש עוד דרישות).

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

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

    שים לב שבפועל כמות ה Unit-Test צריכה להיות שווה ל :
    N(N+1 ) /2
    כאשרN הוא כמות המודולים בתוכנה הסיבה נעוצה בעובדה שאתה צריך לבדוק את כל הקשרים האפשריים .

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

    וזה עוד לפני שבדקנו שהקוד שנכתב הוא תחת 5 המודולים האפשרים לבדיקה שאם תשתמש ביותר הרי שהקוד שלך כבר לא יהיה ממש בדיק .

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

  6. ik_5 מאת

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

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

    לגבי חישוב מתמטי שיכול להיות נכון: ניקח לך דוגמה מבאג שהיה ב X שלקח משהו כמו 20 שנה למצוא:
    if user = GetRootUser()

    מתמטית בגלל ש C יזין ערך ל user התוצאה תהיה תמיד חיובית למרות שפועל יש כאן באג.

    נגיד ואני יוצר תוכנית שמדפיסה לי על המסך את לוח הכפל. עכשיו אני בודק ש1 *5 מחזיר לי 5, אני בודק ש 5* 0 מחזיר לי 0 וכו', החישוב נכון, אבל התוכנית לא מדפיסה את לוח הכפל אלא מדפיסה כפולות מסויימים שבמקרה נמצאים בבדיקה גם. האם הבדיקה באמת מכסה את כל האפשרויות ?

    העניין הוא שאם אני יוצר בדיקה שאומרת האם המשתמש הוא root ורק תעשה משהו, ואני בודק האם בפועל זה מתקיים, הרבה יותר נכונה מאשר לבדוק הם user שווה ל root. למה ? כי אם על משתמש "יוסי" קיבלתי הרשאות root למרות שהוא בכלל מוגדר כ nobody, אז יש לי באג. והיה הרבה יותר ממוקד למצוא אותו.

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

  7. Boris Shtrasman

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

    בנוגע לבאג של משתמש הרוט
    תקן אותי אם אני טועה אבל איך התנאי הבא נכון לוגית ?:
    הוכחה לא פרמלית (ויתקנו אותי המהנדסים) אבל נקבל אותה –

    נגדיר p כuser
    נגדיר q כ פונקציה המחזירה הרשאות משתמש
    נגדיר בv את קיום התנאי

    נגידר את התנאי (שיוויון ) כ p וגם q גורר V
    השוואה מקורית (מה שמתבצע בקוד)
    p=q
    p->v
    p/\p=>v

    p=q
    q->v
    q/\q->v

    מתקבלת המערכת הבא :
    p->v
    q->v

    וזה גורר ל p או q גורר V

    אבל התנאי שהתבקש היה p וגם q גורר V מכאן קיבלנו סתירה .
    קל לראות שמתמטית זה לא נכון .

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

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

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

    בנוגע לחישוב כאשר אתה בונה גרף תגדיר איפה אסור לעבור ואת האורך של קשת (לדוגמה ניתן לתת משקל כבד יותר לנסיעה בגהה מאשר ב471 בגלל הפקקים)

  8. ik_5 מאת

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

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

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

    בקשר לאלגוריתם, באקדמיה יש נטייה לקפוץ מעל הפופיק. במקום לחשוב פיזית למטרה הם קופצים על הרעיון. מה הכוונה ? אם אני צריך עכשיו לקחת 100 רשומות ממסד נתונים ולהתחיל להדפיס אותם על המסך בצורה מסויימת, אז האקדמיה תבקש הרבה מעבר לזה, כמו בהינתן ש….. וכאן זורקים מליון תנאים. בצורה הזו אתה לא יכול לצאת מהבעיה, כי תמיד אתה מוסיף עוד נטבח. אתה יודע מצויין בתור מישהו עם ניסיון עבודה מעשי, שבמציאות לא מעניין אם זה O(n^2) או O(N) t אם זה עושה את העבודה ומגיב כמו שצריך, זה מצויין. אתה רוצה לשפר ? בשלב הבא כשירצו לשנות דברים אולי תצליח.
    אתה מבין ? אלגוריתם טוב זה אלגוריתם שעושה את העבודה. לא בגלל שהוא יעיל, לא בגלל שיש לו שם מפוצץ של חוקר, אלא בגלל שהוא מבצע את מה שהוא צריך.

    ב2002 יצרתי משהו שלפני אף אחד לא יצר מכל האנשים שאני מכיר. לקחתי מידע משתנה של שדה ששמתי ב iframe וקראתי את הערכים של השדה עם javascript שלחתי אותם בתוך hidden כאשר לחצו submit. זה היה לפני עידן ה ְAJAX, והייתי צריך לרענן מידע לפי הרבה פרמטרים כאשר ריענון הדף היה יכול להרוס הרבה מאוד דברים, ולכן זה היה הפתרון. כאשר כולם אמרו שלא הגיוני שזה יעבוד, בפועל זה עבד ועבד טוב. אז נכון שיש דרכים טובות יותר (היום בעיקר) אבל הצלחתי למצוא פתרון לבעיה במקום שכל פתרון מקובל אחר היה שובר את השימשו של האתר. וזה אלגוריתם לכל דבר ועניין, רק לא בצורה אקדמאית.

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

  9. פינגבק: מלפפון עברי « לראות שונה

  10. רובי ירו בי

    אני לא מבין מה הקשר בין בדיקת יעילות לבדיקת התנהגות.
    אם כבר השתמשתי במילה "התנהגות" – ברובי לפחות, שבה קיים הכלי המצויין RSpec, עדיף להתייחס לBDD מאשר לTDD.
    דרך אגב, אפשר להשתמש בRSpec גם כשמתכנתים בC:
    http://www.benmabey.com/2007/09/09/bdd_your_c
    אבל BDD נראה לי עסק מאוד בעייתי כשצריך לקמפל, אז אני אתייחס רק לרובי.
    אני גם מנסה לעבוד בצורה הזאת וגם אני נתקל בקשיים, אבל יש לי כמה נקודות שאולי יעזרו:
    1. האפיונים לא אמורים לתאר כל קלט אפשרי וכל מצב אפשרי.
    2. למתחילים כדאי לכתוב יותר מדי אפיונים מאשר פחות מדי, גם אם זה יוצא פי 20 מהקוד עצמו. אני מאמין שבמשך הזמן לומדים מה באמת צריך ומה לא.
    3. באפיונים בדרך כלל לא רצוי לשמור על העיקרון של DRY. המשמעות היא ששינוי קטן עלול לגרור שכתוב של הרבה שורות אפיון, אבל צריך לזכור שזה בסדר.
    4. השיטה שהכי עוזרת ללמוד, לדעתי – לא להתחיל פרוייקט חדש לגמרי בBDD, אלא לבחור פרוייקט קיים שאתה מוכן לכתוב מחדש ולשדרג אותו.
    קרא את הקוד, חשוב על המשמעות של כל קטע קטנטן, כתוב אפיון בהתאם ואז כתוב את הקוד החדש. זכור לעשות זאת בצעדים קטנים (כמו שאמורים לעשות תמיד בBDD).

  11. פינגבק: בדיקות אוטומטיות אינן התשובות להכל | לראות שונה

כתיבת תגובה

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

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s