בדיקה מקומית נותנת משוב מהיר בזמן העבודה. בפרויקט משותף צריך לבדוק גם שינויים שמגיעים ממחשבים אחרים. נלווה שינוי שעשינו לו push ל-GitHub דרך בדיקה שנכשלת ועד התיקון, ונקרא את ה-workflow של GitHub Actions שמריץ את הבדיקה.
אנשים יכולים לתרום מסביבות שונות. אחד משתמש בסוכן שהגדרנו, אחר בכלי אחר, ומישהו עושה שינוי קטן בדפדפן. הפרויקט צריך דרך לבדוק את השינויים האלה, שלא תלויה בכך שלכל תורם יש אותו hook מקומי.
אינטגרציה רציפה, או CI, פירושה לשלב שינויים בבסיס קוד משותף לעיתים קרובות, ולהשתמש בבנייה ובבדיקות אוטומטיות כדי למצוא בעיות מוקדם. שירות בדיקה משותף תומך בשיטת העבודה הזאת. הוא מציג את התוצאה יחד עם השינוי, כך שהצוות רואה איזו גרסה נבדקה ויכול לחקור כישלון.
זה משנה את הדרך שבה צוות עובד. דמיינו כמה מפתחים שעובדים כל אחד לבד במשך שבועות, ואז מחברים את כל השינויים שלהם יחד לקראת שחרור גדול. כשמשהו נשבר, יש להם הרבה שינויים לבדוק, ואולי יגלו שהקוד שלהם נשען על הנחות שלא מתיישבות זו עם זו.
עם אינטגרציה רציפה, אנחנו משלבים שינויים קטנים בבסיס הקוד המשותף לאורך כל הפיתוח. בנייה וטסטים אוטומטיים נותנים לנו משוב על כל שינוי, כשהעבודה עוד טרייה ויש פחות קוד לבדוק. אפשר לתקן בעיה לפני שעוד עבודה נבנית מעליה. אספקה רציפה מרחיבה את הרעיון: התוכנה נשארת מוכנה לשחרור. הצוות יכול לשחרר עדכונים קטנים יותר כשהם מוכנים, בלי לדחות את כל השילוב והבדיקות לשחרור גדול בסוף. לשלב שינוי לא אומר שחייבים לשחרר אותו מיד למשתמשים. בואו נלווה שינוי קטן אחד דרך הבדיקות שתומכות בשיטת העבודה הזאת.
לפני שננסה, כמה זה עולה? המכונות שמריצות את ה-jobs שלנו נקראות ב-GitHub בשם runners. ה-runners הרגילים ש-GitHub מספק חינמיים למאגרים ציבוריים. במאגרים פרטיים, החבילה GitHub Free כוללת כרגע אלפיים דקות בחודש, שמתחלקות בין כל המאגרים הפרטיים של בעל המאגר. בחבילות אחרות המכסות שונות. המכסה החינמית הזאת שימושית לפרויקטים אישיים קטנים. על runners גדולים יותר ועל שימוש מעבר למכסה משלמים בנפרד.
נניח שאנחנו מעדכנים פרויקט Python קטן שסוכם כמויות של פריטים. הפונקציה צריכה להחזיר את הסכום, אבל השינוי שלנו מחליף אותו בטעות באפס. עבור רשימה שמכילה שתיים ושלוש, אמורים לקבל חמש. בואו נראה מה קורה אם נעשה קומיט לטעות הזאת ו-push ל-GitHub. הגדרנו את המאגר כך שיריץ את בדיקות הפרויקט בכל פעם שאנחנו עושים push ל-main.
אנחנו עושים קומיט לקובץ ששונה ו-push ל-main. GitHub Actions מתחיל הרצה של ה-workflow עבור הקומיט שעשינו לו push. בלשונית Actions של המאגר, אפשר לפתוח את ההרצה הזאת ולראות את השלבים ואת הפלט שלהם. כך לכל מי שעובד על הפרויקט יש גישה לאותה תוצאת בדיקה.
ה-workflow מריץ את פקודת הבדיקה של הפרויקט. הטסט מצפה לחמש ומקבל אפס, ולכן הוא נכשל. הכישלון הזה מוצמד לקומיט שעשינו לו push. אפשר לראות את הפקודה ואת הפלט שלה בלי לבקש מהתורם להעתיק הודעה מהטרמינל שלו.
מה הפעיל את הבדיקה הזאת? קובץ workflow במאגר אומר ל-GitHub Actions מתי לרוץ ומה לבצע. בואו נקרא את הקובץ שמריץ את בדיקות הפרויקט שלנו.
workflow מתאר את האוטומציה. הוא מכיל jobs, וכל job מכיל שלבים. ל-workflow הראשון שלנו יש job אחד. השלבים שלו מביאים את הקוד, מגדירים את Python, מתקינים את התלויות ומריצים את בדיקת הפרויקט.
runner הוא המכונה שמריצה job. בדוגמה הזאת, GitHub מקצה runner חדש על השרתים שלו, והשלבים של ה-job רצים שם אחד אחרי השני. כל שלב מריץ פקודת shell או משתמש ב-action. action הוא קוד לשימוש חוזר, למשימה כמו לעשות checkout למאגר או להגדיר את Python.
זה קובץ YAML. מפתח עם נקודתיים אחריו פותח ערך או קטע מקונן, וההזחה מראה מה שייך לתוכו. השדה name מזהה את ה-workflow. הקטע on קובע אילו אירועים מפעילים אותו. כאן, pull request אומר ש-GitHub מריץ את ה-workflow בכל pull request, לפני שאפשר למזג אותו. ו-push אומר שהוא רץ גם כשאנחנו עושים push לקוד, והשדה branches מגביל את זה ל-main. צריך לעשות לקובץ ה-workflow קומיט ו-push יחד עם הפרויקט. כל push נוסף ל-main מפעיל הרצה נוספת.
השדה permissions נותן לטוקן של ה-workflow הרשאה לקרוא את תוכן המאגר. תחת jobs, checks הוא השם של ה-job שלנו, ו-runs-on בוחר runner של Ubuntu ש-GitHub מספק. בתוך ה-job הזה אנחנו מפרטים את השלבים שמכינים את המכונה ומריצים את הבדיקות.
השלבים מופיעים בהמשך אותו קובץ, בתוך ה-job בשם checks. כל מקף פותח שלב נוסף ברשימה. uses מציין action. checkout מביא את הקבצים של המאגר, ו-setup-python מתקין את Python 3.12, אותה גרסה שבחרנו גם לדוגמה המקומית. המספר שאחרי הסימן @ הוא הגרסה של ה-action עצמו, לא הגרסה של Python.
run מריץ פקודת shell על ה-runner. קודם מתקינים את תלויות הפיתוח מתוך requirements-dev.txt. אחר כך מריצים את אותו סקריפט בדיקה שאנחנו מריצים מקומית. אם אחד מכלי הבדיקה נכשל, הסקריפט מסתיים בסטטוס של כישלון. זה מכשיל את השלב הזה, ו-GitHub מדווח שה-job נכשל.
ה-runner עושה checkout למאגר ופועל לפי הוראות ההכנה של ה-workflow. הוא לא מעתיק את תיקיית העבודה של הלפטופ שלכם או את החבילות שמותקנות בו. אז אם התוכנית שלכם צריכה את data.csv שמופיע כאן ולא נכנס לקומיט, הקובץ הזה יחסר. גם חבילה שהתקנתם מקומית צריכה להיכלל בהכנה של ה-runner.
לפני שמשנים את הקוד, קוראים מה נכשל. ב-GitHub, פותחים את לשונית Actions של המאגר, ואז את ההרצה שנכשלה, ואז את השלב שנכשל, כדי לראות את הפלט שלו. אם התקנת התלויות נכשלה, בדיקות הפרויקט לא התחילו בכלל. אם טסט מדווח על assert שנכשל, הוא רץ אבל קיבל תוצאה לא צפויה. ה-CLI של GitHub יכול להדפיס את אותו פלט בטרמינל. כך גם הסוכן יכול לקרוא אותו בשבילכם.
בדוגמה שלנו, הטסט רץ כמו שצריך אבל מצא תשובה שגויה. שתיים ועוד שלוש צריך לצאת חמש, והפונקציה החזירה אפס. אנחנו משחזרים את הכישלון מקומית, משנים את הפונקציה כך שתחזיר את הסכום, ומריצים שוב את הבדיקות. עברו גם על ה-diff, כדי לראות מה גרם לטסט לעבור.
עשו קומיט לתיקון ו-push ל-main. ה-push הזה מפעיל הרצה חדשה של ה-workflow על הקוד המתוקן.
שימו לב לסדר. GitHub מקבל את ה-push שלנו, ורק אז מריץ את הבדיקות. הרצה שנכשלה מדווחת על בעיה, אבל היא לא מסירה את הקומיט ולא מבטלת את ה-push. בדוגמה הזאת, התגובה שלנו היא לחקור את הכישלון ולעשות push לתיקון. הרצת בדיקות מקומית קודם עוזרת לנו לתפוס טעויות לפני שהן מגיעות לענף המשותף.
בדיקה מקומית תלויה בכל תורם ותורם. כדי ששינוי שנכשל לא יגיע לאנשים שמשתמשים במאגר, הבדיקה צריכה לעבור לפני שהשינוי מגיע ל-main. ב-GitHub, ruleset עבור main יכול לדרוש pull request ולדרוש שבדיקות הפרויקט יעברו לפני המיזוג. עם הכלל הזה, push ישיר ל-main נדחה. השינוי עובר דרך pull request, והמיזוג שלו נשאר חסום כל עוד בדיקת החובה נכשלת. ב-main נשארים רק קומיטים שעברו, כך שכל מי שמשכפל אותו מקבל קוד שעבר את הבדיקות.
צריך גם לתחזק את מה שהבדיקות האלה מכסות. אם שינוי מסיר טסט או מחליש את סקריפט הבדיקה, ה-job יכול לעבור בלי לבדוק משהו שהצוות עדיין צריך. עברו על שינויים ב-workflow, בסקריפט הבדיקה ובטסטים, יחד עם השינויים בקוד של האפליקציה.
משוב מקומי עוזר לתקן טעות מהר, כשהקוד הרלוונטי עדיין מול העיניים. ה-workflow ב-GitHub בודק את הקוד שעשינו לו push, בסביבה שהוגדרה לו. הוא יכול לחזור על בדיקות שכבר הרצתם מקומית. החזרה הזאת מועילה, כי התוצאה המשותפת לא תלויה בדיווח מלפטופ אחד.
לצד בדיקות שהסוכן מפעיל, אפשר להריץ בדיקה מקומית בכל פעם שמישהו עושה קומיט ב-Git. ל-Git יש hooks משלו לפעולות כאלה. hook של pre-commit רץ לפני ש-Git יוצר את הקומיט, בין אם סוכן מריץ את הפקודה ובין אם אדם. התרשים מפריד בין ה-hooks האלה של Git לבין ה-hooks של הסשן ברתמת הסוכן.
git commit --no-verify מדלג על ה-hook של pre-commit. גם אדם וגם סוכן יכולים להשתמש באפשרות הזאת. hook בסשן של הסוכן יכול לדחות פקודה שהוא מזהה, אבל הוא מכסה רק את הסשן הזה. כשאנחנו עושים push ל-main, GitHub מריץ את בדיקות ה-workflow גם אם דילגו על הבדיקה המקומית.
אחרי שהבדיקות עוברות, פרויקט יכול גם להכין שחרור. כאן נכנסות לתמונה אספקה רציפה ופריסה רציפה. אספקה רציפה דואגת ששינויים שנבדקו יהיו מוכנים לשחרור, ומשאירה את השחרור עצמו כהחלטה נפרדת. workflow של שחרור יכול לאכוף את אותו סדר. הוא מפרסם גרסה רק ב-job שדורש שה-job של הבדיקות יעבור.
פריסה רציפה משחררת שינויים אוטומטית אחרי שה-pipeline עובר. הצוות צריך גם לנטר את התוכנה שרצה ולהתאושש מבעיות. הדוגמה שלנו נעצרת בבדיקה של הקוד שעשינו לו push.
עכשיו אתם יכולים להריץ בדיקות פרויקט ב-GitHub אחרי push, ולהשתמש בתוצאות כדי לחקור כישלון. ל-runner של ה-CI יש זיכרון של דג זהב, והסטנדרטים של רס״ר קשוח שלא מרפה לרגע. נתראה בסרטון הבא.