ما الذي تدين به رسالة الفشل لقارئها؟
متى كانت آخر مرة قرأت فيها — فعلاً قرأت — رسالة فشل اختبار؟
في الغالب كانت الثانية صباحاً، والسجل مفتوح أمامك، وكل ما لديك سطر واحد:
Expected True, but found False.
أيّ True؟ في أيّ استدعاء؟ في هذا الاختبار تسعة تأكيدات، وأيُّها كان يمكن أن ينتج هذا السطر نفسه. الرسالة لا تستبعد شيئاً.
وعامل البناء الذي نفّذ الاختبار اختفى أصلاً. والفرع قد يُحذف الأسبوع المقبل. لكن السطر يبقى — ملصقاً في محادثة، مقتبساً في تذكرة، مرفقاً بتقرير ما بعد الحادث. رسالة الفشل هي الجزء الوحيد من مجموعة اختباراتك المضمون قراءته خارج المحرر، على يد من لم يرَ الكود أبداً. لهذا القارئ كُتبت هذه المقالة.
هنا تتوقف مكتبة الاختبارات عن كونها واجهة برمجية وتصبح وثيقة. نحن نختار المكتبة بواجهتها المرنة — Should() و.Be() و.And — لكن ما نقرأه فعلاً، ونحن تحت الضغط، بعد شهور، وبلا سياق، هو نص الفشل. سمِّ الشيء باسمه: هذا هو سطح التشخيص لمجموعة الاختبارات كلها. ومع ذلك قلّ من سأل: ما الذي يجب أن يدين به هذا السطح لقارئه؟
جرّبها في ستين ثانية. الادعاء المركزي لهذه المقالة قابل للفحص، فافحصه بنفسك:
dotnet add package Jeninnet.Assertions
using Jeninnet.Assertions;
[TestMethod]
public void Username_matches_expected()
{
string username = "dennis";
username.Should().Be("jonas");
}
Expected username to be 'jonas', but found 'dennis'.
الحزمة لا تعتمد على أي إطار اختبار، فتعمل أسطر التوكيد الثلاثة نفسها تحت MSTest أو xUnit أو NUnit. أعد تسمية username إلى login فيتسمّى معه نص الفشل؛ ونفّذه على جهاز آخر فلا يتحرك النص.
هذه المقالة أول ست ملاحظات عن هذا السطح — وكلمة عن كيفية قراءتها. هذه الملاحظات لن تُعلّمك واجهة المكتبة؛ فالكتاب والمرجع يفعلان ذلك. إنها تشرح منطق التصميم الذي وراءها: لماذا يبدو كل سطح على ما هو عليه، وما ثمنه، وأين يتوقف. كل واحدة تقف وحدها؛ ومجتمعةً تصف تصميماً واحداً.
وخط السير ثابت. بعد هذا العقد: رسائل الفشل بالعربية — ماذا يتغير حين لا يقرأ القارئ الواقع تحت الضغط الإنجليزية. ثم الزمن: كيف تختبر ما يحدث لاحقاً دون أن تنام أثناء تنفيذ مجموعتك. ثم الذاكرة: لماذا تنتمي ميزانيات التخصيص إلى اختبارات الوحدة. ثم الانتظار: اختبارات تستطلع حقيقة لم تصل بعد بدل أن تؤكد كذبة على مؤقّت. وأخيراً: تحقق لا يرمي أبداً — الأفعال نفسها حيث لا يُسمح بالاستثناءات. ابدأ من هنا، من العقد.
سمِّ المتغيّر. أول واجبات رسالة الفشل أن تقول ماذا فشل، لا أن تقول إن شيئاً قد فشل. قارن:
Expected True, but found False.
Expected username to be 'jonas', but found 'dennis'.
الجملة الثانية تنجو حين تُلصق في محادثة فريق بلا أي كود حولها. والاسم هنا مأخوذ من نص التعبير نفسه لحظة التصريف — عبر سمة CallerArgumentExpression في المصرّف (C# 10) — لا من وسم يدوي يفلت من المتغيّر خلال أسبوع، ولا من تتبّع مكدّس يعتمد على ملف PDB قد لا يكون منشوراً أصلاً. وللحكاية وجه ثانٍ يستحق الذكر صراحة: أعد تسمية المتغيّر فيتسمّى معه نص الفشل تلقائياً. الأوسمة اليدوية تتعفن؛ والتعبيرات الملتقطة لا يسعها ذلك.
وبلا الاسم يبدأ كل فشل بالطريقة نفسها: افتح الكود، وعُدّ التوكيدات، وضيّق الدائرة. تسعة مشتبهين وسطر دليل واحد. اسم المتغيّر يختصر هذا البحث إلى الصفر — قبل أن تفتح أي شيء، تعرف أي قيمة، من أي تعبير، أخفقت في أي فحص.
اذكر المتوقَّع والفعلي، وبهذا الترتيب. القارئ يعقد مقارنة؛ سلّمه طرفيها بترتيب القراءة. "فشل التأكيد" حكمٌ بلا ملف قضية.
كن ثابتة. نفس الفشل يجب أن يُنتج النص نفسه على جهازك وجهاز زميلك وعامل البناء — عبر أنظمة التشغيل وثقافات الواجهة. التنسيق الثابت (invariant) ليس لمسة تجميل؛ إنه ما يجعل نص الفشل قابلاً للبحث والمقارنة بين التشغيلات، وآمناً لأن يُؤكَّد عليه داخل اختبارات تختبر الاختبارات. مجموعة اختبارات هذه المكتبة نفسها — 1242 اختباراً كلها خضراء — تؤكد رسائلها كنصوص حرفية حيث تكون مستقرة، وهذا ممكن فقط لأن الرسائل صُممت مستقرة أصلاً. الثبات خاصية قابلة للاختبار، وهي مُختبَرة فعلاً.
وتأمل البديل الذي تعيش عليه معظم المجموعات: رسائل تتغير بتغير ثقافة واجهة الجهاز، فيقرأ الفشل نفسه على حاسوبك بصيغة وعلى عامل البناء بصيغة أخرى. لا يمكنك البحث في سجل لا تستطيع إعادة إنتاجه، ولا التأكيد على نص يتحرك. الثبات هو ما يجعل رسالة الفشل تتصرف كبيانات — قابلة للمقارنة والبحث والاقتباس — لا كأحوال طقس.
تصاغَرْ بأناقة. رسالة الفشل ملخّص لا تفريغ بيانات. حين تتجاوز النصوص المقارَنة مئتي حرف أو تعبر الأسطر، طبعُها كاملة يدفن الفرق؛ ما ينفع هو الفرق نفسه: diff موحّد بثلاثة أسطر سياق، والقيم مقتطعة عند مئة حرف. وحين تكون القيمة وحدها طويلة، اقتطعها ودع القارئ يعيد الإنتاج بنفسه. الرسالة تجيب عن سؤال واحد: هل تحتاج إلى إعادة الإنتاج، أم أنك ترى الخلل أصلاً؟
أشِر إلى الخطوة التالية. بعض الإخفاقات بحثٌ أخطأ العنوان — مفتاح قاموس، خاصية JSON، سطر في مجموعة نصوص. سرد الموجود، مرتباً بالقرب ومحدوداً بثلاثة، يحوّل الطريق المسدود إلى خطوة عملية:
Did you mean 'name'?
اخدم القارئين معاً. الإنسان يقرأ نصاً، وخط الأنابيب يقرأ JSON. المكتبة الجدّية في سطح التشخيص تعامل الاثنين كصياغتين لبنية واحدة: كل فشل مجموع يحمل تعبير الموضوع وفئته ورسالته، والقيمتين الفعلية والمتوقعة حيث يبلّغ عنهما الفعل — يُسلسل للآلة ويُصاغ للإنسان. ابنِ السطحين منفصلين فيتباعدا مع الزمن؛ وابنِهما من مصدر واحد فيبقيا صادقين.
وعملياً، تسافر الصياغة الآلية أبعد مما يتوقع معظم المهندسين: مُرفقة بتشغيلات التكامل، مخزنة إلى جوار تقارير التشغيل الجاف، تُقارن عبر البناءات الليلية. تقرير JSON يحمل التعبير والفئة والقيمتين هو ما يجعل الفشل مقروءاً للأدوات بعد عام، حين لا يذكر أحد الاختبار.
قل "لماذا" حين يسأل القارئ لماذا. فشلٌ يصف الحالة فقط هو نصف تشخيص. من أرفق because: "the connection dropped" يحصل على جملة تفكير مترابطة، لا على معلومتين متجاورتين. جملة السبب تُلحَق — بأسلوب الجملة نفسها — بما بلّغ عنه الفعل أصلاً؛ فيركب السبب مع الدليل بدل أن يحلّ محله.
لا شيء من هذا يغني عن المصفّح، ولا قيمة لأي منه إن كان الاختبار لا يؤكد شيئاً. رسالة الفشل آخر الملاذ — ولهذا تحديداً تستحق تصميماً من الدرجة الأولى. فآخر الملاذ هو حيث ينتهي بنا الجميع.
ويبقى قارئ واحد لم يخدمه هذا العقد بعد: المهندس الذي ليست الإنجليزية لغته العاملة. نص الفشل من الأسطح النادرة التي نضمن أن قارئها تحت ضغط — ومكتبات هذا المجال تكاد تُجمع على لغة واحدة، وما عداها يصل مترجَماً. ما الذي يدين به سطح التشخيص لهذا القارئ؟ هذا موضوع المقالة التالية.
التالي: حين يجب أن تتكلم رسالة الفشل بالعربية
عن هذه السلسلة: هذه الملاحظات من بناء Jeninnet.Assertions — مكتبة تأكيدات مرنة ومستقلة عن أطر الاختبار، بترخيص MIT وبلا أي اعتماديات خارجية. هذه المقالة 01 من 06.












