
كيف تُعيد هيكلة كود بلا اختبارات؟
ورثتَ مئة ألف سطر من كود قديم. لا اختبارات. تريد إعادة الهيكلة لكنك لا تعرف ما الذي سينكسر إن لمسته. لكتابة الاختبارات تحتاج فهم الكود، ولفهم الكود تحتاج وثائق — والوثائق غير موجودة.
لا أحد يلمسه. يزداد تعفّنًا.
كل كود قديم في العالم عالق في هذا الجمود. تنفق شركات Fortune 500 ما بين 60% و80% من ميزانياتها التقنية على صيانة الأنظمة القديمة. يُهدر 42% من وقت المطورين في معالجة الديون التقنية.
ماذا لو استطاع LLM كتابة الاختبارات بدلًا منك؟
المشكلات حين تُوكل الاختبارات إلى LLM
حين تطلب من LLM “اكتب اختبارًا لهذه الدالة”، يخرج شيء ما. المشكلة ثلاثية:
أولًا، لا يعرف من أين يبدأ. حين يكون لديك 527 دالة — هل تبدأ بالأولى بالترتيب؟ بالأهم؟ لا معيار.
ثانيًا، لا يمكن التحقق من جودة الاختبار. كتب LLM اختبارًا ونجح. لكن هل يتحقق فعلًا من سلوك الدالة، أم أنه مجرد استدعاء بلا assert — قشرة فارغة؟ لن تعرف إلا بقراءته يدويًا.
ثالثًا، بدون تغذية راجعة تتوقف اختبارات LLM عند 60–70%. عبارة “اختبر هذه الدالة” وحدها لا تصل إلى تغطية فرعية 100%. يجب إخباره بالفروع المفقودة ليُكملها.
LLM ليس عاجزًا عن كتابة الاختبارات. المشكلة هي غياب البنية التي تخبره بماذا يكتب ومدى جودة ما كتبه.
tsma: مسار اختبار يعمل بأمر واحد
tsma أداة CLI تفهرس جميع دوال المشروع، وتكتشف وجود الاختبارات، وتقيس التغطية، وتقدم تغذية راجعة دقيقة لوكلاء LLM.
الأمر الوحيد الذي يحتاجه الوكيل:
$ tsma next
هذا الأمر الواحد يُشغّل الحلقة بأكملها:
$ tsma next # يعرض الدالة التالية بلا اختبار
→ تكتب الاختبار
$ tsma next # يكتشف الاختبار الجديد، يُشغّله، يقيس التغطية
→ 100%? PASS، ينتقل للدالة التالية
→ <100%? يعرض الفروع غير المغطاة مع أرقام الأسطر
$ tsma next # يعيد قياس الاختبار المُعدَّل
→ تحسّن أو لم يتحسّن، يُعلَّم DONE وينتقل للتالي
يتكرر حتى يظهر “All functions complete!”.
تم التحقق على 527 دالة
طُبّق tsma على مشروع Go حقيقي (527 دالة).
| النتيجة | العدد | النسبة |
|---|---|---|
| PASS (تغطية فرعية 100%) | 246 | 46.7% |
| DONE (أفضل جهد) | 281 | 53.3% |
| TODO (لم تُعالَج) | 0 | 0% |
246 دالة وصلت إلى تغطية فرعية 100%. الـ 281 المتبقية لم تصل إلى 100%، لكن كُتبت لها اختبارات بأقصى قدر ممكن.
لماذا لا تصل بعض الدوال إلى 100%؟
الدوال التي تصل إلى 100% والتي لا تصل
ما إذا كانت الدالة تستطيع الوصول إلى تغطية فرعية 100% يعتمد على كيفية تلقّيها لاعتمادياتها.
واجهة (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 الخارجية. لا يمكنك إثارة خطأ محدد أو إرجاع نتيجة محددة. الفروع التي تعتمد على تلك النتائج لا يمكن الوصول إليها باختبارات الوحدة.
استجابة tsma: بعد تغذية راجعة عن الفروع غير المغطاة، يحاول مرة أخرى. إن لم يصل، يقبلها كـ DONE. هذا ليس قصورًا في الأداة بل انعكاس لقابلية الكود للاختبار. إدخال الواجهات (DI) يجعل 100% ممكنًا، لكن ذلك يعني تعديل الكود الأصلي.
التغذية الراجعة تُغيّر اختبارات LLM جذريًا
القيمة الجوهرية لـ tsma ليست الفهرسة ولا قياس التغطية. إنها الإشارة الدقيقة إلى الفروع غير المغطاة بأرقام الأسطر.
بدون تغذية راجعة:
"اكتب اختبارًا لدالة ListContracts"
→ LLM يختبر المسار السعيد فقط
→ تغطية 60–70%
مع تغذية راجعة:
"اكتب اختبارًا لدالة ListContracts"
→ تغطية 65% (11/17)
→ UNCOVERED:
line 41 — if params.Status != nil
line 44 — if params.BuildingId != nil
line 70 — if err != nil (CountSummary)
→ LLM يضيف اختبارات تغطي تلك الفروع تحديدًا
→ تغطية 100%
نفس LLM. الفرق الوحيد هو وجود التغذية الراجعة. ثلاثة أسطر من أرقام الأسطر تفصل بين 60% و100%.
حتى لو مات الوكيل، التقدم محفوظ
وكلاء LLM يتعطلون. حد الرموز، أخطاء الشبكة، انقطاع الجلسة. لا يمكن معالجة 527 دالة في جلسة واحدة.
tsma يحفظ حالة التقدم بشكل دائم في .tsma/session.json.
$ tsma status
527 functions
PASS: 246 (46.7%)
DONE: 281 (53.3%)
TODO: 0 (0.0%)
إن توقف الوكيل عند الدالة رقم 200؟ وكيل جديد ينفّذ tsma next ويكمل من الرقم 201. ملف session.json هو نقطة الحفظ.
عدة وكلاء يعملون بالتناوب دون تعارض. العملية ذرّية على مستوى الدالة.
الجلسة ذاكرة مؤقتة، وملفات المصدر هي الحقيقة
أحد مبادئ تصميم tsma: الجلسة ذاكرة مؤقتة (cache) وملفات المصدر هي مصدر الحقيقة (source of truth).
إن حذفت ملف اختبار، حتى لو كان session.json يسجّله كـ PASS، تعود الدالة إلى TODO. الجلسة لا تنفصل عن الواقع.
المبدأ:
حتى لو قال session.json "PASS"
إن لم يوجد ملف اختبار → TODO
إن تغيّر ملف المصدر → يُعاد القياس
التعليمات لوكيل LLM
الوكيل يحتاج 6 أسطر فقط:
1. نفّذ tsma next
2. إن كان TODO — اقرأ الدالة واكتب اختبارًا
3. إن فشل الاختبار — اقرأ الخطأ وعدّل الاختبار
4. إن ظهرت فروع غير مغطاة — أضف اختبارات تغطيها
5. إن كان PASS/DONE — الدالة التالية تظهر تلقائيًا
6. كرّر حتى يظهر "All functions complete!"
الأمر الوحيد الذي يحتاجه الوكيل هو tsma next. الباقي تفرضه أداة CLI.
القطار والسكة
الـ vibe coding قطار. سريع. لكن بلا سكة ينحرف.
أدوات البرمجة بالذكاء الاصطناعي كلها تركّز على جعل القطار أسرع. نماذج أكبر، وكلاء أذكى، prompts أفضل. لكن كلما ازدادت سرعة القطار، ازداد ضرر الانحراف.
tsma هو السكة. LLM يولّد الاختبارات (Neural)، وCLI يحدد “إلى هنا فقط” (Symbolic Constraint). إبداع LLM يبقى حرًّا، لكن جودة النتائج تفرضها الآلة.
| التقليدي | tsma | |
|---|---|---|
| كتابة الاختبارات | الإنسان (بطيء) أو LLM (فوضوي) | LLM يكتب، CLI يتحقق |
| من أين نبدأ؟ | الإنسان يقرر | CLI يحدد الترتيب |
| فحص الجودة | الإنسان يراجع | CLI يقيس التغطية |
| التغذية الراجعة | لا شيء | أرقام أسطر الفروع غير المغطاة |
| تتبع التقدم | لا شيء | session.json تلقائيًا |
LLM يولّد بحرية. لكنه يسير على سكة tsma next فقط.
دعم اللغات
| اللغة | المُفهرِس | مُشغّل الاختبارات | التغطية |
|---|---|---|---|
| Go | go/ast | go test | go test -coverprofile |
| TypeScript | regex | npx vitest / npx jest | c8 / istanbul |
| Python | regex | pytest | coverage.py |
Go يستخدم محلل AST لاستخراج دقيق للدوال. 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