בדיקות מוטציה הן שליליות. בדיקות TDD הן חיוביות.
לשתיהן קוראים בדיקות, אבל הן טוענות דברים הפוכים. בדיקת TDD אומרת מה המערכת חייבת לעשות. ריצת מוטציות יכולה רק לדווח מה מערך הבדיקות שלכם לא שם לב אליו. לדעת איזה סימן אתם מחזיקים קובע מה לעשות עם התוצאה, וגם אם אפשר לסמוך על בדיקה שסוכן כתב.
שני סוגי טענה
בדיקה מוכוונת בדיקות נכתבת לפני שההתנהגות קיימת. היא נכשלת, אחר כך עוברת, ומאז היא טענה קבועה: הקלט הזה חייב לייצר את התוצאה ההיא. מערך הבדיקות מצטבר למפרט שאפשר לקרוא, והכתיבה מראש מפעילה לחץ על העיצוב, כי קוד שקשה לקרוא לו קשה לבדוק. ריצת מוטציות עושה משהו אחר במבנה שלו. היא לוקחת קוד שכבר עובד, משנה אותו בכוונה, ומריצה שוב את המערך. כל תוצאה מנוסחת בשלילה: השינוי הזה לא נתפס. שום דבר בפלט הזה לא אומר בשביל מה התוכנה קיימת.
מערך TDD ירוק הוא אוסף של טענות על כוונה. ציון מוטציה גבוה הוא היעדר של סוג מסוים של הוכחה נגד מערך הבדיקות שלכם. רק אחד מהשניים הוא מפרט.
הבדיקה החיובית
- + נכתבת לפני הקוד, כך שהדרישה קיימת במילים לפני שהיא קיימת בלוגיקה
- + נכשלת קודם, וזו ההוכחה הזולה היחידה שהבדיקה בכלל מסוגלת להיכשל
- + מנסחת כלל, ולכן שורדת שינויי מבנה: הפנימיות יכולות לזוז בלי שהטענה תזוז
- + מפעילה לחץ על העיצוב בזמן שהעיצוב עוד זול לשינוי
- + נקראת כתיעוד בידי האדם הבא, כולל הסוכן הבא
הבדיקה השלילית
- − רצה בדיעבד, על קוד ועל בדיקות שכבר קיימים
- − מוטנט שנהרג לא מוסיף מידע; רק שורדים נושאים אות
- − שורד הוא עדות לעיוורון, ולא עדות לפגם במוצר
- − לא אומרת דבר על דרישות חסרות, רק על חוסר רגישות לשינויים שנוסו
- − מייצרת דוח ורשימת מטלות, לא נכס שמישהו שומר
אותה מילה, שני מכשירים שונים
| הבדיקה החיובית | הבדיקה השלילית | |
|---|---|---|
| מה היא טוענת | המערכת חייבת להתנהג כך. | מערך הבדיקות שלכם לא שם לב לשינוי הזה. |
| מה ירוק מוכיח | כל התנהגות שאופיינה עד כה ממומשת. | המערך רגיש לקבוצת האופרטורים הזו. שום דבר על נכונות. |
| מה נשאר אחריה | בדיקות, ועיצוב שעוצב בגלל הצורך לכתוב אותן. | דוח. צריך לחלץ ממנו את הערך לפני שהוא מתיישן. |
| מתי היא רצה | כל הזמן, בקצב של דקות, בזמן כתיבת הקוד. | ב-CI: הדרגתית על קבצים שהשתנו, או כסריקה לילית. |
| מודל העלות | משולם בהתמדה ונטמע בתוך הפיתוח. | מוטנטים כפול זמן הרצת הבדיקות הרלוונטיות. גדל בשניהם. |
| השפעה על העיצוב | לחץ של יכולת בדיקה. עיצוב מסורבל כואב מיד. | אין. היא מדרגת את מה שכבר קיים. |
| איך הכישלון מגיע | אדום, בכוונה, בכתיבתכם, צעד אחר צעד. | שורד שאף אחד לא כתב, ושצריך כעת לפרש. |
איך באמת מריצים את זה
לבדיקות מוטציה יש מוניטין של יקרות מדי, וזו כמעט תמיד בעיית היקף ולא בעיית כלים. הסדר הזה שומר עליהן זולות מספיק כדי לשרוד מפגש עם לוח זמנים של אספקה.
קודם להעמיד כיסוי
- - בדיקות מוטציה על קוד לא מכוסה רק מדווחות על המובן מאליו, ובעלות גבוהה
- - הכיסוי הוא השער הזול והמתמשך; המוטציה נשמרת לקוד שכבר מכוסה
- - בחרו מודול אחד שחשוב — תשלומים, הרשאות, חישוב מחיר — ולא את כל המאגר
לתחום כל ריצה
- - ניתוח כיסוי לכל בדיקה הוא המנוף הגדול: רצות רק הבדיקות שמגיעות לכל מוטנט
- - ב-pull requests להריץ מוטציה רק על קבצים שהשתנו, במצב הדרגתי; זו ריצה של דקות
- - את הסריקה המלאה להשאיר בלוח זמנים, מחוץ לנתיב הקריטי, שם זמן ארוך לא עולה לאף אחד
למיין שורדים לשלושה סלים
- - דרישה חסרה, שהופכת לבדיקה חיובית חדשה שנקראת על שם הכלל
- - מוטנט שקול שלא משנה דבר נצפה, שמתועד ומוחרג פעם אחת
- - קוד שאף דרישה לא מבקשת, שנמחק — ההריגה הזולה ביותר
לשים שער על נסיגה, לא על מספר
- - קבעו ערך break שעוצר את ירידת הציון, ועצרו שם
- - דווחו על שורדים כפריטי סקירה ולא ככשלי בנייה, כדי שאף אחד לא ילמד להתעלם מבנייה אדומה
- - עקבו אחרי זמן ריצה כמדד מדרגה ראשונה; שער שמכבים בגלל איטיות לא מגן על כלום
מה משתנה כשמודל שפה כותב את הקוד
סוכנים לא יצרו את הבעיה הזו, אבל הם תיעשו אותה. שאלת הסימן מפסיקה להיות פילוסופיה ברגע שלבדיקות ולמימוש יש אותו מחבר, והמחבר הוא מודל.
בקשו מסוכן פיצ'ר עם בדיקות ותקבלו את שניהם, שנוצרו מקריאה אחת של הדרישה, בהקשר אחד. אם הקריאה הייתה שגויה, הבדיקות מקודדות את אותו אי-הבנה ועוברות — והכיסוי נראה מצוין, כי כל שורה שהמודל כתב מורצת בידי בדיקה שהמודל כתב כדי להריץ אותה. זה בדיוק הכשל שסקירה רגילה תופסת הכי פחות טוב, כי הבדיקות נראות סבירות ב-diff. בדיקות מוטציה הן הבדיקה האוטומטית הזולה ביותר שתופסת אותו, מסיבה אחת: הן לא שואלות אם יש בדיקות אלא אם הן מגיבות. בדיקה שמשקפת את המימוש לא תשים לב שהמימוש השתנה.
להכניס את זה ללופ, לא לדוח
מוטנט ששרד הוא משוב טוב במיוחד לסוכן: הוא בר-הרצה, ספציפי, ואי אפשר לתרץ אותו. פסקת עצות בסקירה מקבלת אישור והתעלמות; שינוי מוגדר שלא זוהה מתוקן. זו בדיוק צורת תפקיד ה-hardener ב-SwarmForge של אנקל בוב, שמריץ את כלי המוטציה קובץ אחר קובץ ואסור לו להתקדם עד שהשורדים טופלו.
להעביר את הקריטריון להוראת הכתיבה
השורה הטובה ביותר בפרויקט ההוא נמצאת בפרומפט תפקיד ה-coder: כתוב בדיקות שהיו נכשלות עבור מימוש שגוי סביר. זה קריטריון מוטציה שמוטמע בשלב הייצור, וזה זול בהרבה מלגלות את אותו דבר בביקורת שעה אחר כך. הכניסו את המשפט הזה להוראות הסוכן שלכם.
להפריד בין המחבר ובין המקשיח
אותו מודל, באותו הקשר, יסביר למה שורד מסוים בסדר, כי ההיגיון שיצר את הפער עוד לפניו. הריצו את מעבר המוטציה כשלב נפרד עם הקשר נפרד ובלי גישה להצדקה המקורית — רק ה-diff ופלט הכלי.
לצפות למרדף אחרי הציון במהירות מכונה
אם אומרים לסוכן להעלות את ציון המוטציה, הוא יכתוב גלאי שינויים שהורגים מוטנטים מהר משאיש יכול לסקור. חוק גודהארט חמור יותר עם סוכנים, כי הנפח בחינם. תנו לסוכנים לדווח על שורדים ולהציע בדיקות ברמת הדרישה; ההחלטה מהי הדרישה נשארת אצל אדם או אצל תפקיד מפרט.
להעריך קוד שסוכן בדיוק הפיק
כיסוי וציון מוטציה יחד הם מדריך שימושי לעבודה שנכתבה בידי AI. קראו אותם כזוג — המידע המעניין נמצא בחוסר ההסכמה ביניהם.
| אות | מה זה אומר בדרך כלל | מה לעשות |
|---|---|---|
| כיסוי גבוה, ציון מוטציה נמוך | הבדיקות נכתבו כדי להריץ את הקוד, לא כדי לבדוק אותו. הצורה הקלאסית של בדיקות שמודל מייצר. | השאירו את המימוש בסקירה, וזרקו או כתבו מחדש את הבדיקות ברמת הדרישה. |
| שניהם גבוהים, ב-diff קטן | עבודה טובה באמת, או גלאי שינויים מחופשים היטב. | בדקו מדגמית spies, snapshots וטענות על מספר קריאות. אם הטענות מנסחות כללים, אפשר לשלוח. |
| שורדים מרוכזים במסלולי שגיאה | המודל מימש כראוי את המסלול המאושר ואת השאר סיפר. | אפיינו במפורש את התנהגות הכשל, ואז בקשו בדיקות על האפיון הזה. |
| שורדים בקוד שאף דרישה לא מנסחת | הכללה ספקולטיבית — אפשרויות, דגלים והסתעפויות הגנתיות שאף אחד לא ביקש. | מחקו את הקוד. זו מורכבות לא נבדקת בלי בעלים. |
| הציון עלה, והבדיקות נוגעות עכשיו בפנימיות | הסוכן ייעל את המדד בכך שהלחים את המערך למימוש של היום. | לדחות. השינוי המבני הבא ישבור את הבדיקות האלה בלי ששום התנהגות תשתנה. |
| רשימת החריגות של מוטנטים שקולים גדלה מהר | הסוכן מתווכח עם הכלי במקום לשפר את המערך. | קראו את ה-diff בעצמכם. החרגות הן החלטה אנושית, פעם אחת, עם סיבה מתועדת. |
אותו שורד, שתי תגובות
כאן הסימן מפסיק להיות פילוסופיה. דוח המוטציות מוסר לכם מקום; מה שתכתבו אחר כך יקבע אם המערך יתחזק או רק יתקשח.
הקוד, והמוטנט ששורד
jsהסרת .trim() היא מוטציה סטנדרטית של קריאה למתודה. היא שורדת בכל פעם שאין fixture עם רווחים בקצוות, כלומר ברוב ה-fixtures — כולל אלה שמודל המציא לקוד של עצמו.
// importer.js
const normalise = (value) => value.trim().toLowerCase();
export function importRows(rows) {
return rows.map((row) => ({ email: normalise(row.email) }));
}
// Surviving mutant: normalise() with .trim() removed.
// The suite passes either way, so the report flags it. להרוג את המוטנט, ולצמד את המערך
jsהבדיקה הזו הורגת את השורד. היא גם מקפיאה את המימוש הנוכחי: normalise חייבת להישאר נגישה ולהיקרא בדיוק כך. שנו את שמה, שלבו אותה פנימה או העבירו אותה מעבר לגבול, והבדיקה תישבר בעוד ההתנהגות לא השתנתה. סוכן שמייעל את הציון מייצר את הצורה הזו כברירת מחדל.
it('calls normalise once per row', () => {
const spy = vi.spyOn(internals, 'normalise');
importRows([{ email: ' Ada@Example.COM ' }]);
expect(spy).toHaveBeenCalledTimes(1);
});
// Green. Mutant dead. Score up.
// Nothing here states what an imported email address should look like. לענות על השאלה שהשורד שאל
jsאותו מוטנט, אותה הריגה, אבל הטענה היא כלל שגם מנהל מוצר יכול לקרוא. היא עוברת דרך הפונקציה הציבורית, כך שהפנימיות נשארות חופשיות לזוז, והיא מסבירה את עצמה בהודעת הכישלון גם בעוד חצי שנה.
it('trims and lower-cases every imported email address', () => {
expect(importRows([{ email: ' Ada@Example.COM ' }]))
.toEqual([{ email: 'ada@example.com' }]);
});
// Same mutant dead, but the suite gained a specification
// instead of a snapshot of today's call graph. לשמור את הביקורת זולה מספיק כדי להמשיך להריץ
jsonניתוח כיסוי לכל בדיקה הוא מנוף הביצועים הגדול ביותר: רצות רק הבדיקות שמגיעות בפועל לכל מוטנט. מצב הדרגתי משאיר ריצת pull request בטווח של דקות. ערך ה-break הוא רצפה נגד נסיגה, לא יעד לרדוף אחריו.
{
"testRunner": "vitest",
"coverageAnalysis": "perTest",
"incremental": true,
"mutate": ["src/**/*.js", "!src/**/*.test.js"],
"thresholds": { "high": 80, "low": 60, "break": 60 }
} השער לענף שסוכן כתב
shשתי שאלות עצמאיות, שנשאלות בידי שלב שלא כתב את הקוד: האם הקוד הזה בצורה שמאפשרת לשנות אותו, והאם הבדיקות שמגנות עליו מגיבות כשהוא משתנה? שתי התשובות עולות ל-pull request, ואף אחת מהן לא נתונה למשא ומתן עם המחבר.
CHANGED=$(git diff --name-only origin/main... -- 'src/**/*.js')
# structural: complexity against coverage on touched methods
crap-report --changed-only --threshold 30 $CHANGED || exit 1
# behavioural: do the new tests notice anything?
stryker run --incremental --mutate "$CHANGED"
# survivors are review items with a named requirement attached,
# never a licence to write a test that pins the call graph. איפה השלילי הופך רעיל
לרדוף אחרי הציון
מהרגע שציון המוטציה הוא יעד, נכתבות בדיקות שהורגות מוטנטים במקום בדיקות שמנסחות דרישות. הן עוברות סקירה כי הן ירוקות, והן בדיוק אלו שנשברות בשינוי המבנה הבא, למרות שההתנהגות לא השתנתה.
להיאבק במוטנטים שקולים
יש מוטציות שמשנות את הקוד בלי לשנות התנהגות נצפית. שום בדיקה כנה לא יכולה להרוג אותן. תעדו, החריגו, והמשיכו. הזמן כאן קונה מספר, לא ביטחון.
לטעון את המוטציה במקום את הכלל
אם אתם לא מצליחים לנסח את הדרישה שבדיקה חדשה מגנה עליה, כתבתם גלאי שינויים. הוא ידווח על כל עריכה עתידית ככישלון וילמד את הצוות להפסיק לקרוא את פלט הבדיקות.
לתת למחבר להקשיח את עבודתו
מודל שכתב את הקוד ימצא לכל שורד סיבה למה הוא מקובל, כי ההיגיון שלו עוד נראה לו סביר. הקשחה חייבת לקרות בשלב שרואה רק את ה-diff ואת פלט הכלי.
להפוך כל שורד לטענה חיובית
לקרוא את השורד כשאלה
- - איזו התנהגות הייתה צריכה להיות נכונה כדי שהשינוי הזה ישבור משהו?
- - איזו דרישה, אם מישהו היה כותב אותה, הייתה נכשלת עכשיו?
- - מי במורד הזרם היה מרגיש אם המוטציה הזו הייתה מגיעה לפרודקשן?
לכתוב את התשובה, לא את ההריגה
- - לקרוא לבדיקה על שם הכלל, לא על שם המוטנט או מספר השורה
- - לטעון דרך הממשק הציבורי כדי שהפנימיות יישארו חופשיות להשתנות
- - אם אפשר להגיע לכלל רק בחשיפת הפנימיות, זה ממצא עיצובי ולא ממצא בדיקות
או למחוק את הקוד
- - אם אין דרישה שניתן לנסח, שום דבר לא באמת תלוי בהתנהגות הזו
- - שורד בקוד לא מאופיין הוא לרוב דרישה מתה ולא בדיקה חסרה
- - מחיקה היא ההריגה הזולה ביותר, והיא גם מזרזת את הביקורת הבאה
אחת כותבת את המפרט, השנייה מבקרת אותו
בדיקות מוטציה אינן TDD משופר ואינן תחליף לו. הן לא מייצרות בדיקות, לא עיצוב ולא הצהרת כוונה. מה שהן מייצרות הוא רשימת מקומות שבהם המפרט שלכם פחות רגיש ממה שהנחתם, וזה ערך אמיתי וגם דבר אחר לגמרי.
חלוקת העבודה הזו חשובה עכשיו יותר מאשר כששתי המשימות היו של אותו מהנדס. בדיקות עוד צריכות להיווצר דרך TDD, כטענות חיוביות על התנהגות — גם כשסוכן כותב אותן, ולכן ההוראה לכתוב בדיקות שהיו נכשלות עבור מימוש שגוי סביר שייכת לפרומפטים שלכם. ריצות מוטציה מבקרות אחר כך את הטענות האלה מהקשר נפרד שאין איתו ויכוח. ואף שורד לא הולך ישר לבדיקה: קודם מתרגמים אותו לדרישה, או מוחקים את הקוד שבו הוא יושב. ממצא שלילי הופך לערך מתמשך רק כשמישהו הופך אותו לטענה חיובית על מה שהתוכנה קיימת בשבילו.
מאמרי הנדסה קשורים
לכיסוי ולמורכבות יש מדד משל עצמם, ושתי הפרקטיקות כבר הוקצו לתפקידים נפרדים בנחיל סוכנים שעובד בפועל.
מדד CRAP: איתור מורכבות לא נבדקת
בשביל מה CRAP קיים, איך QA מכוון בעזרתו בדיקות ושערי שחרור, ואיך לשפוט אם מודל שפה כותב קוד שאפשר לתחזק.
SwarmForge לעומק: איך נחיל הסוכנים של אנקל בוב עובד באמת
ניתוח מעמיק של צינור התפקידים, דמון ההעברות ושערי האיכות הניתנים להרצה של SwarmForge, עם פסק דין מפורש על מה לאמץ.
שאלות נפוצות
האם בדיקות מוטציה טובות יותר מכיסוי קוד?
הן עונות על שאלה חזקה יותר במחיר גבוה בהרבה. כיסוי סופר אם שורה הורצה; מוטציה שואלת אם משהו בכלל נבדק. השתמשו בכיסוי כשער זול ומתמשך, ובבדיקות מוטציה כביקורת תקופתית על קוד שחשוב — וכבדיקה קבועה על כל דבר שסוכן כתב.
איך בדיקות מוטציה עוזרות עם קוד שנוצר בידי AI?
הן תופסות בדיוק את הכשל שסקירה מפספסת: בדיקות שנוצרו מאותו אי-הבנה כמו המימוש, שעוברות ומייצרות כיסוי מצוין בלי לבדוק דבר. לבדיקות מוטציה לא משנה אם יש בדיקות, אלא רק אם הן מגיבות לשינוי בקוד, ולכן מערך שמשקף את המימוש נחשף מיד.
האם סוכנים צריכים להריץ בדיקות מוטציה בעצמם?
כן, כשלב נפרד מזה שכתב את הקוד, ועם שורדים שמטופלים כשאלות ולא כציון למקסום. תנו לסוכן לדווח על שורדים ולהציע בדיקות ברמת הדרישה; את ההחלטה מהי הדרישה השאירו לאדם או לתפקיד מפרט, אחרת תקבלו גלאי שינויים במהירות מכונה.
לאיזה ציון מוטציה כדאי לשאוף?
ציונים מעל 80 אחוז נחשבים בדרך כלל חזקים, ו-60 עד 80 אחוז סבירים עם פערים אמיתיים, אבל למספר יש משמעות רק ברמת מודול. כלל שימושי יותר הוא בלי נסיגה בקבצים שהשתנו, עם ערך break שעוצר את הידרדרות הציון.
באילו כלים צוותים משתמשים?
PIT הוא הבחירה המבוססת ב-JVM, Stryker Mutator מכסה JavaScript, TypeScript, C# ו-Scala, וב-Python מקובלים mutmut ו-cosmic-ray. כולם מאפשרים לצמצם ריצה לקבצים שהשתנו, וזה מה שהופך את הפרקטיקה לכדאית ברמת pull request.
איפה בדיקות מוטציה משתלבות ב-CI?
הדרגתית ב-pull requests, מוגבלת לקבצים שהשתנו עם ניתוח כיסוי לכל בדיקה, ובנוסף סריקה מלאה לפי לוח זמנים. דווחו על שורדים כפריטי סקירה ולא ככשלי בנייה, ושימו שער רק על נסיגה בציון.