בדיקות הן תוצרי בנייה, ומערכת הבנייה צריכה להיות הבעלים שלהן
רוב הפרויקטים מתייחסים לבדיקות כטקס נפרד. יש את הבנייה, שמייצרת את הבינאריים, ואז יש את "הרצת
הבדיקות", שהיא פקודה אחרת, שמורצת בזמן אחר, בדרך כלל במנגנון אחר: מריץ בדיקות, סקריפט של CI, יעד
במייקפייל בשם test שאינו תלוי בדבר ואינו בונה דבר מחדש. השניים מחוברים במוסכמה ובהרגל, לא בשום דבר
שהכלים מבינים.
אני חושב שזה הפוך, והסיבה אינה אסתטית. בדיקה היא תוצר בנייה כמו כל תוצר אחר: יש לה קלטים, יש לה פלט, והפלט מתיישן בדיוק כשהקלטים משתנים. ברגע שאומרים זאת כך, מערכת הבנייה היא הבעלים המובן מאליו, ומריץ הבדיקות הנפרד הוא מימוש מחדש, גרוע, של מעקב אחר תלויות.
מה בדיקה היא באמת
הפשיטו בדיקה למה שהיא עושה. היא מקבלת קבוצת קלטים, הקוד הנבדק, קוד הבדיקה עצמו, קבצי נתונים או תשתית, ומייצרת פלט: עבר או נכשל, ועוד יומן. זו בדיוק הצורה של צעד קומפילציה. הקומפיילר מקבל קבצי מקור וכותרות ומייצר קובץ אובייקט; הבדיקה מקבלת קבצי מקור ונתונים ומייצרת פסק דין.
מערכת הבנייה כבר יודעת לטפל בצורה הזו. היא יודעת מה תלוי במה. היא יודעת שאם נגעתם ב-parser.c אז
צריך לבנות מחדש את parser.o, ואחריו כל מה שמקשר את parser.o. מה שבדרך כלל לא אומרים לה הוא
שגם את test_parser.passed צריך לבנות מחדש, ושדבר אחר אינו תלוי ב-parser.c באופן מעניין.
אמרו לה. הפכו את תוצאת הבדיקה לקובץ:
test_parser.passed: test_parser parser.o fixtures/parser_cases.txt
./test_parser && touch $@
check: test_parser.passed test_lexer.passed test_io.passed
עכשיו מערכת הבנייה עושה את השאר בחינם. געו בקובץ נתונים ורק הבדיקות שקוראות אותו ירוצו שוב. געו
בכותרת שבשימוש בכל מקום וכל הבדיקות ירוצו שוב, כפי שצריך. אל תיגעו בדבר ו-make check יסתיים בזמן
שלוקח לבדוק את המצב של כמה קבצים.
מדוע המריץ הנפרד הוא הכלי הלא נכון
החלופה הרגילה היא מריץ בדיקות שמגלה כל בדיקה ומריץ את כולן, בכל פעם. יש לו שני אופני כישלון, והם מושכים לכיוונים מנוגדים.
הוא מריץ יותר מדי. בפרויקט גדול הסדרה המלאה לוקחת דקות או שעות, ולכן אנשים מפסיקים להריץ אותה מקומית. הם דוחפים ומחכים שמכונת ה-CI תאמר להם, עשרים דקות אחר כך, שהם שברו משהו שיכלו לתפוס בשתי שניות. סדרה שאיטית מדי מכדי להריץ היא סדרה שאינה מורצת, ובדיקה שאינה מורצת היא תיעוד.
ואז הוא מריץ מעט מדי. כדי להתמודד עם הבעיה הראשונה אנשים מתחילים לבחור: להריץ רק את הבדיקות
בתיקייה הזו, רק את הבדיקות המסומנות fast, רק את הבדיקות שנראות לי רלוונטיות. עכשיו הבחירה נעשית על
ידי אדם שמנחש תלויות, שזו בדיוק העבודה שמערכת הבנייה קיימת כדי לעשות באופן מכני. הניחוש שגוי לעיתים
קרובות דיו כדי שהכישלון של עשרים הדקות ב-CI יחזור.
מריץ בדיקות אינו יכול לפתור את זה מפני שאינו מכיר את גרף התלויות. הוא יודע אילו קבצים הם בדיקות. הוא אינו יודע לאילו בדיקות חשוב הקובץ שזה עתה שיניתם. מערכת הבנייה יודעת, מפני שהידע הזה הוא כל תוכנה של מערכת בנייה.
ההתנגדויות
לבדיקות יש תופעות לוואי. לחלקן יש: הן כותבות לבסיס נתונים, הן תופסות פורט, הן זקוקות לשירות שרץ. אלה בדיקות אינטגרציה, וצריך למדל אותן כמה שהן, תוצר שתלוי בסביבה. קודדו את הסביבה כקלט אם אתם יכולים, ואם אינכם יכולים, לפחות גרמו לבנייה להיכשל בקול רם ולא להעמיד פנים. לרוב הבדיקות אין את הבעיה הזו, ואלה שיש להן אינן טיעון בעד טיפול גרוע בתשעים האחוזים האחרים.
בדיקות מהבהבות ייכנסו למטמון כעוברות. בדיקה שעוברת פעם אחת ונכשלת אחר כך בלי שינוי בקלט אינה בדיקה, היא מחולל מספרים אקראיים, ושמירת הפלט שלה במטמון חושפת זאת ולא גורמת לזה. התרופה היא לתקן את ההבהוב, וגם כאן מערכת הבנייה עוזרת: כשמעבר שנשמר במטמון הופך לכישלון בלי ששום דבר במעלה הזרם השתנה, מצאתם הבהוב בוודאות במקום בחשד.
הבנייה נעשית איטית יותר. רק בפעם הראשונה. אחר כך היא נעשית מהירה יותר מכל מריץ, מפני שהיא עושה את המינימום.
הנקודה העמוקה יותר
הסיבה שזה חשוב מעבר לנוחות היא שזה משנה את משמעות המילה "נבנה". עץ שבו הבנייה הצליחה אבל הבדיקות לא הורצו נמצא במצב לא ידוע, וכולנו למדנו להתייחס אליו כטוב בכל זאת, מפני שהבדיקות היו הבעיה של מישהו אחר. קפלו את הבדיקות לתוך הבנייה ובנייה מוצלחת פירושה שהבדיקות שיכלו להיות מושפעות מהשינוי שלכם עברו. לא יותר, לא פחות, ונכון באופן מכני ולא באופן מלא תקווה.
זו הדרך השפויה ביותר לחבר בדיקות לתלויות שלהן, מפני שזו הדרך היחידה שמחברת אותן בכלל. כל השאר הוא אדם שזוכר לעשות זאת.