Free

دع Claude يكتب الاختبارات، وأنت راجِع فقط: 1٬562 سطرًا من التطبيق العملي

كُتبت تقريبًا جميع أسطر اختبارات TopicReview البالغة 1٬562 بواسطة Claude. أنا أقوم فقط بالمراجعة — أكثر من أسبوعين في الإنتاج، وجميع الإصدارات اللاحقة تضيف اختبارات ولا تُعيد كتابتها. يتناول هذا المقال سبب كون الاختبارات هدفًا مثاليًا للتفويض، وما يُنظر إليه وما يُتجاهل أثناء المراجعة (بما في ذلك حالة حدية مفقودة فعلًا في الـ spec)، والإعداد الافتراضي لهذا التقسيم.


أغلق المقال السابق بعبارة «119 spec خضراء» لنظام TopicReview. السؤال الحقيقي الذي يطرحه القارئ بعدها: من كتب تلك الاختبارات؟

الجواب: كتب Claude تقريبًا جميع الـ 1٬562 سطرًا من كود الاختبار. أنا أقوم بالمراجعة فقط. بعد أكثر من أسبوعين على الإنتاج، بات نمط صيانة هذه الـ 1٬562 سطرًا إضافة اختبارات جديدة فقط، دون إعادة كتابة الاختبارات القديمة.

يتناول هذا المقال سبب كون الاختبارات أفضل مهمة لتفويضها لـ Claude، وما يُنظر إليه وما يُتجاهل عند المراجعة، وإلى أي مدى ينجح هذا التقسيم في الواقع.

قبل كل شيء، ليكن أمامنا الأرقام

اختبارات TopicReview موزعة على 7 ملفات:

spec/services/topic_review_service_spec.rb   760 سطرًا (88 اختبارًا)
spec/requests/topic_reviews_spec.rb          281 سطرًا (32 اختبارًا)
spec/requests/review_appeals_spec.rb         152 سطرًا (16 اختبارًا)
spec/requests/review_votes_spec.rb           127 سطرًا
spec/policies/topic_review_policy_spec.rb    109 أسطر
spec/jobs/close_topic_review_job_spec.rb      71 سطرًا (7 اختبارات)
spec/models/topic_review_spec.rb              62 سطرًا
───────────────────────────────────────────
                                          1٬562 سطرًا

تغطي أربعة أنواع من الاختبارات: service (منطق الأعمال)، request (controller + integration)، policy (تفويض Pundit)، job (المهام المجدولة).

الإصدار الأول d162f1e يحمل Co-Authored-By: Claude Sonnet 4.6 ويُنزل أكثر من 1٬100 من تلك الأسطر دفعةً واحدة. كل إصدار spec لاحق يحمل «Add test for...»—لا إصدار واحد يحمل refactor أو rewrite:

00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning
3b185da Update specs to use PROVISIONAL_PENALTY constant

رقع للثغرات، لا إعادة عمل. هذه التفصيلة مهمة وستعود لاحقًا.

لماذا الاختبارات أفضل مهمة للتفويض

أربعة أسباب صلبة:

1. المدخلات والمخرجات صريحة. الاختبار في جوهره «مع حالة كذا → توقع سلوكًا كذا». هذا المجال الأقوى عند Claude: ترجمة المواصفة إلى توكيد. كود الأعمال قد يتطلب أحيانًا مفاضلات، أما الاختبارات فلا تحتاج ذلك عادةً.

2. ميكانيكي × بحجم كبير. يحتاج describe .open! واحد إلى تغطية «يوجد محلفون مؤهلون / لا يوجد / لا يوجد topic / يوجد review فعّال»—أربعة contextات، كل منها 2–5 it. يبدأ البشر في القطع عند الـ context الثالث. أما Claude فيكتب it رقم 88 بنفس الدقة التي كتب بها الأول.

3. حلقة تغذية مرتدة قصيرة جدًّا. تكتب اختبارًا، تُشغّل rspec، وفي ثوانٍ تعرف هل نجح أم فشل. كود الأعمال يحتاج أيام استخدام حقيقي لإظهار المشكلات. الحلقة القصيرة تعني أن خطأ Claude يُضبط فورًا بواسطة rspec—لا داعي للمراقبة.

4. متواز بطبيعته. كتل it مستقلة، دون تزاوج خفي، وسهلة التوسيع. توليد عشرات الاختبارات المنعزلة دفعةً واحدة تحديدًا ما يجيده Claude.

ما الذي يُنظر إليه أثناء المراجعة وما الذي يُتجاهَل

هذا هو محور التقسيم بأكمله.

يُتجاهَل:

  • صحة بنية RSpec → Claude لا يخطئ تقريبًا
  • جودة الـ mock → باستثناء over-mock الواضح، يكون الأمر جيدًا
  • جماليات الـ factory → ليست مهمة، المهم أن يعمل
  • اتساق الأسلوب → إذا وُجد خلل، اطلب من Claude تعديلًا شاملًا بتعليمة واحدة

يُنظر إليه:

  • هل الحالات الحدّيّة مغطاة فعلًا
  • هل أسماء الاختبارات تصف السلوك المتوقع الحقيقي
  • هل هناك اختبار ينبغي أن يوجد لكنه مفقود

البند الأخير هو القيمة الحقيقية للمراجعة. يغطي Claude «ما يتبادر إلى ذهنه» من اختبارات، لكن ما لا يتبادر إلى ذهنه لا يُكتب تلقائيًّا. هنا تضع المراجعة البشرية نفسها—السير عكسيًّا من قواعد الأعمال إلى التغطية الناقصة.

مثال ملموس: ما الذي تلتقطه المراجعة فعلًا

افتح بداية describe ".open!" في spec/services/topic_review_service_spec.rb:

describe ".open!" do
  context "when there are eligible jurors" do
    # حالة الـ review سليمة / post under_review / إنشاء assignments / إبلاغ author / إبلاغ jurors / لا فتح مزدوج
  end
  context "when there are no eligible jurors" do
    # يُنشأ review لكن لا تُنشأ assignments
  end
  context "when post has no topic" do
    # يُعيد nil
  end
end

يبدو شاملًا. لكن القاعدة الفعلية لـ eligible_jurors في model تستبعد ثلاث فئات:

def eligible_jurors
  excluded_ids = [ post.user_id ] + post.reports.pluck(:user_id) + review_votes.where(stage: :initial).pluck(:user_id)
  User.jurors_and_judges.where.not(id: excluded_ids.uniq)
end

لننظر إلى الاختبارات الآن—أيّ اختبار يؤكد أن «كاتب المنشور لا يُختار أبدًا كعضو في هيئة المحلفين»؟

تفحّص service_spec.rb وmodel_spec.rb: لا يوجد. يختبر model_spec.rb بضع حالات من scope pending_vote_by فقط، ولا يغطي eligible_jurors مباشرةً. في service_spec.rb يوجد تعليق وحيد: # Jurors must NOT be the post author—ذلك في setup، لا في توكيد.

هذا ما تلتقطه المراجعة: ثلاث قواعد استبعاد (الكاتب / المبلِّغ / من صوّت مسبقًا في initial)، ولا قاعدة منها محميّة باختبار. إن قام أحدهم لاحقًا بإعادة هيكلة eligible_jurors وأسقط عن غير قصد post.user_id من قائمة الاستبعاد، ستمر جميع الاختبارات الحالية بنجاح—ويسمح الإنتاج بهدوء للمؤلفين بالجلوس في هيئة محلفيهم الخاصة.

Claude لم يخطئ—اختبر ما طُلب منه اختباره. لكنه لم يسأل من تلقاء نفسه: «هل تحتاج كل من هذه القواعد الثلاث إلى تغطية اختبار؟» هذا السؤال—السير من القواعد إلى التغطية—هو ما تقوم به المراجعة.

(بصراحة: غاب عني هذا في المراجعة الأولى. اكتشفته فقط عند إجراء مراجعة ثانية أثناء كتابة هذا المقال. إذن المراجعة ليست ضربة واحدة—لكنها مع ذلك أفضل 10 أضعاف من غيابها.)

الإصدارات اللاحقة تؤكد واقعية هذا التقسيم

لو كان «Claude يكتب + إنسان يُراجع» مثاليًّا، لما وُجدت إصدارات اختبار جديدة بعد الأول. لكن ما حدث أشدّ إثارة—رقع الثغرات دون إعادة كتابة:

00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning

الأول regression test بعد bug—e8cb2db Default to keep verdict when review expires with zero votes هو الإصلاح، و00393fc هو الاختبار المرافق. النمط ذاته ينطبق على الثاني التابع لـ abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.

يثبت هذان الإصداران أمرين في آن واحد:

  • لم تلتقط المراجعة 100% من الحالات—لذا كشف الإنتاج عن اثنين من الأعطال
  • لكن بنية الاختبار صمدت وبإمكاننا متابعة الإضافة دون إعادة هيكلة—لذا كانت الإصدارات «Add test for...» وليس «Rewrite ... spec»

«كافٍ + يمكن مواصلة الترقيع» معيار أكثر واقعية من «مثالي» بفارق كبير. ملاحقة المراجعة المثالية هي ما يجعلك تعجز عن تفويض الاختبارات لـ Claude. قبول «كافٍ» هو ما يُطلق التقسيم إلى العمل.

اختبارات لا ينبغي تفويضها لـ Claude منفردًا

ليست كل الاختبارات صالحة للتفويض الكامل:

  • مسارات E2E السعيدة—تحتاج عدسة المنتج. يستطيع Claude كتابتها لكنه يميل لتغطية «تكتمل تقنيًّا» ويفوّت «أين يتعثر المستخدم فعلًا»
  • اختبارات الأمان—تحتاج عقلية المهاجم. Claude متحفّظ، يفوّت أسطح هجوم غير قياسية (حقن كلمات SQL، سلاسل مفرطة الطول، unicode بديل)
  • الأرقام المرجعية للأداء—تحتاج أرقامًا من بيئة نشر حقيقية. Claude يرمي عتبات عشوائية
  • إعادة هيكلة كبيرة لـ fixture / factory—هذا تغيير على مستوى المعمار؛ يعود إلى plan mode، ليس شيئًا تلتقطه المراجعة

في هذه الحالات يقود الإنسان ويُساعد Claude.

الإعداد الافتراضي

جعل هذا التقسيم default قابل للتنفيذ:

  1. قبل بدء الميزة، أشرح قواعد الأعمال (لا اصطلاحات RSpec)
  2. يكتب Claude التطبيق والاختبارات
  3. نُشغّل الاختبارات. النجاح = متابعة. الفشل = يُصلح Claude بنفسه
  4. أقوم بالمراجعة:
    • لا أنظر إلى البنية/الـ mock/الـ factory
    • أنظر إلى التغطية: هل كل قاعدة أعمال محميّة على الأقل باختبار واحد؟
    • استجواب الحالات الحدّيّة: «0 صف / null / تزامن / انتهاك صلاحية»—واحدة تلو الأخرى
    • قراءة أسماء الاختبارات—إن لم يكن الاسم يخبرني بما يُختبَر، أطلب من Claude إعادة التسمية
  5. الأخطاء المكتشفة في الإنتاج تعود على شكل regression tests—هذا استنزاف طبيعي للتقسيم، لا فشل

الخاتمة

موارد المبرمج النفسية لقراءة الاختبارات أقل منها لقراءة الكود. الاختبارات متكررة، ميكانيكية، منهكة، وضرورية. كل ذلك يصف بقعة قوة Claude تمامًا—لا يمل، لا يتعب، لا يقصّر في الـ it رقم 50.

عملك ليس «كتابة الاختبارات»—بل «ضمان تغطية كل قاعدة أعمال باختبار». إحداهما تطبيق والأخرى حكم. يبقى الحكم معك؛ أما التطبيق فيذهب إلى Claude.

119 spec / 1٬562 سطرًا تُشحن في إصدار واحد وتصمد أكثر من أسبوعين دون rework—لم يحدث لأنني أكتب اختبارات أفضل، بل لأنني لم أكتب أيًّا منها. أقوم بشيء واحد لا يقوم به Claude: أقرر أي قواعد الأعمال تستحق الحماية.