
איך מבצעים refactoring לקוד בלי טסטים?
קיבלתם בירושה 100,000 שורות קוד ישן. בלי טסטים. רוצים לעשות refactoring, אבל לא יודעים מה ישבר אם נוגעים. כדי לכתוב טסטים צריך להבין את הקוד, וכדי להבין את הקוד צריך תיעוד — שלא קיים.
אף אחד לא נוגע. הקוד ממשיך להירקב.
כל קוד ישן בעולם תקוע בקיפאון הזה. 60–80% מתקציב ה-IT של חברות Fortune 500 מוקדש לתחזוקת מערכות ישנות. 42% מזמן המפתחים מוקדש לטיפול בחוב טכני.
מה אם LLM יכול לכתוב את הטסטים במקומכם?
הבעיות כשמפקידים טסטים בידי LLM
כשאומרים ל-LLM “כתוב טסט לפונקציה הזו”, משהו יוצא. הבעיה משולשת.
ראשית, לא יודעים מאיפה להתחיל. כשיש 527 פונקציות — לפי הסדר מהראשונה? הכי חשובה קודם? אין קריטריון.
שנית, אי אפשר לאמת את איכות הטסטים. הטסט שה-LLM כתב עבר. אבל האם הטסט הזה באמת מוודא את התנהגות הפונקציה, או שזה סתם קריאה לפונקציה בלי assert אחד? צריך לקרוא כל טסט ידנית כדי לדעת.
שלישית, בלי משוב ה-LLM נתקע ב-60–70%. רק עם “כתוב טסט לפונקציה הזו” לא מגיעים ל-100% branch coverage. צריך לומר ל-LLM אילו ענפים חסרים כדי שישלים את השאר.
ה-LLM לא חסר יכולת לכתוב טסטים. הבעיה היא שאין מבנה שאומר ל-LLM מה לבדוק וכמה טוב הוא בדק.
tsma: מסילת טסטים שרצה בפקודה אחת
tsma הוא כלי CLI שמאנדקס את כל הפונקציות בפרויקט, מזהה קיום טסטים, מודד coverage ונותן ל-LLM agent משוב מדויק.
הפקודה היחידה שה-agent צריך לדעת:
$ tsma next
הפקודה הזו מניעה את כל הלולאה:
$ tsma next # מציגה את הפונקציה הבאה ללא טסט
→ כותבים טסט
$ tsma next # מזהה את הטסט החדש, מריצה, מודדת coverage
→ 100%? PASS, הלאה לפונקציה הבאה
→ <100%? מציגה ענפים לא מכוסים עם מספרי שורות
$ tsma next # מודדת מחדש את הטסט המתוקן
→ השתפר או לא, מסמנת DONE וממשיכה הלאה
חוזרים עד שמופיע “All functions complete!”.
אומת על 527 פונקציות
tsma הופעל על פרויקט Go אמיתי עם 527 פונקציות.
| תוצאה | מספר | אחוז |
|---|---|---|
| PASS (100% branch coverage) | 246 | 46.7% |
| DONE (best-effort) | 281 | 53.3% |
| TODO (לא טופל) | 0 | 0% |
246 פונקציות הגיעו ל-100% branch coverage. 281 הנותרות לא הגיעו ל-100%, אבל טסטים נכתבו עד כמה שניתן.
למה יש פונקציות שלא מגיעות ל-100%?
פונקציות שמגיעות ל-100% — ואלה שלא
האם פונקציה יכולה להגיע ל-100% branch coverage תלוי באיך היא מקבלת את התלויות שלה.
Interface (mockable) — 100% אפשרי:
type Handler struct {
svc AuthSvc // interface — ניתן להחליף ב-mock
}
בטסט מזריקים mock ושולטים בכל הנתיבים:
svc := mocks.NewMockAuthSvc(ctrl)
svc.EXPECT().Login(...).Return(result, nil) // נתיב הצלחה
svc.EXPECT().Login(...).Return(nil, err) // נתיב כישלון
טיפוס קונקרטי (not mockable) — 100% בלתי אפשרי:
type Handler struct {
svc *service.SMSImportService // מצביע ל-struct — לא ניתן להחלפה
}
המימוש האמיתי רץ עם תלויות פנימיות כמו בסיס נתונים או API חיצוני. אי אפשר לגרום לשגיאה ספציפית או לתוצאה ספציפית. ענפים שתלויים בתוצאות כאלה לא נגישים ב-unit test.
התגובה של tsma: אחרי משוב על ענפים לא מכוסים, ניסיון נוסף. אם עדיין לא מגיעים — מסומן כ-DONE. זו לא מגבלה של הכלי אלא שיקוף של יכולת הבדיקה של הקוד. עם interfaces (DI) אפשר להגיע ל-100% — אבל זה דורש שינוי בקוד המקורי.
משוב משנה את הטסטים של LLM באופן דרמטי
הערך המרכזי של tsma הוא לא האינדוקס ולא מדידת ה-coverage. הערך הוא הצגת ענפים לא מכוסים עם מספרי שורות מדויקים.
בלי משוב:
"כתוב טסט לפונקציה ListContracts"
→ LLM בודק רק את ה-happy path
→ coverage 60–70%
עם משוב:
"כתוב טסט לפונקציה ListContracts"
→ coverage 65% (11/17)
→ UNCOVERED:
line 41 — if params.Status != nil
line 44 — if params.BuildingId != nil
line 70 — if err != nil (CountSummary)
→ LLM מוסיף טסטים שמכסים בדיוק את הענפים האלה
→ coverage 100%
אותו LLM בדיוק. ההבדל היחיד הוא המשוב. שלוש שורות עם מספרי שורות מפרידות בין 60% ל-100%.
כשה-agent קורס, ההתקדמות נשמרת
LLM agents קורסים. מגבלת tokens, שגיאת רשת, ניתוק סשן. אי אפשר לטפל ב-527 פונקציות בסשן אחד.
tsma שומר את מצב ההתקדמות בקובץ .tsma/session.json.
$ tsma status
527 functions
PASS: 246 (46.7%)
DONE: 281 (53.3%)
TODO: 0 (0.0%)
ה-agent קרס בפונקציה ה-200? agent חדש מקליד tsma next וממשיך מה-201. session.json הוא ה-checkpoint.
כמה agents יכולים לעבוד לסירוגין בלי התנגשויות. כל פונקציה היא יחידה אטומית.
הסשן הוא cache, קובץ המקור הוא האמת
עיקרון תכנוני של tsma: הסשן הוא cache וקובץ המקור הוא ה-source of truth.
אם מוחקים קובץ טסט, הפונקציה חוזרת ל-TODO — גם אם session.json מסמן אותה כ-PASS. הסשן לעולם לא מתנתק מהמציאות.
עיקרון:
session.json אומר "PASS"?
אין קובץ טסט → TODO
קובץ המקור השתנה → מדידה מחדש
הוראות ל-LLM agent
ה-agent צריך 6 שורות הוראות:
1. להריץ tsma next
2. TODO → לקרוא את הפונקציה ולכתוב טסט
3. טסט נכשל → לקרוא את השגיאה ולתקן את הטסט
4. ענפים לא מכוסים מוצגים → להוסיף טסטים שמכסים אותם
5. PASS/DONE → הפונקציה הבאה מוצגת אוטומטית
6. לחזור עד שמופיע "All functions complete!"
ה-agent צריך לדעת פקודה אחת בלבד: tsma next. השאר נכפה על ידי ה-CLI.
הרכבת והמסילה
Vibe coding זו רכבת. מהירה. אבל בלי מסילה היא יוצאת מהפסים.
כל כלי ה-AI coding מתמקדים בלהפוך את הרכבת למהירה יותר. מודלים גדולים יותר, agents חכמים יותר, prompts טובים יותר. אבל ככל שהרכבת מהירה יותר, הנזק מיציאה מהפסים גדול יותר.
tsma היא המסילה. ה-LLM מייצר טסטים (Neural), ה-CLI מגדיר “עד כאן ולא יותר” (Symbolic Constraint). היצירתיות של ה-LLM נשארת כמות שהיא, אבל איכות התוצאה נכפית על ידי המכונה.
| קודם | tsma | |
|---|---|---|
| כתיבת טסטים | אדם (איטי) או LLM (כאוטי) | LLM כותב, CLI מאמת |
| מאיפה מתחילים? | אדם מחליט | CLI קובע את הסדר |
| בדיקת איכות | אדם עושה review | CLI מודד coverage |
| משוב | אין | ענפים לא מכוסים עם מספרי שורות |
| מעקב התקדמות | אין | session.json אוטומטי |
ה-LLM מייצר בחופשיות. אבל הוא רץ רק על המסילה של tsma next.
תמיכה בשפות
| שפה | indexer | test runner | coverage |
|---|---|---|---|
| Go | go/ast | go test | go test -coverprofile |
| TypeScript | regex | npx vitest / npx jest | c8 / istanbul |
| Python | regex | pytest | coverage.py |
Go משתמש ב-AST parser לחילוץ פונקציות מדויק. TypeScript ו-Python מבוססים על ביטויים רגולריים.
קבצים שנוצרו אוטומטית (*_gen.go, *.pb.go), קבצי טסט ו-vendor/node_modules מוחרגים אוטומטית מהאינדוקס.
התקנה והרצה
make install
cd your-legacy-project
tsma next
זה הכל.
MIT License. github.com/park-jun-woo/tsma