واجهة مشغّل الاختبارات


تقع على مشغّلات الاختبارات مسؤولية واحدة: أخذ حل، وتشغيل جميع الاختبارات، وإرجاع مخرجات موحّدة. تتم كل التفاعلات مع موقع Exercism تلقائيًا وليست جزءًا من هذه المواصفات.

التنفيذ

  • ينبغي أن يوفّر مشغّل الاختبارات سكربتًا قابلًا للتنفيذ. يمكنك العثور على مزيد من المعلومات في ملف docker.md.
  • سيتلقى السكربت ثلاث معاملات:
    • معرّف التمرين (مثل two-fer).
    • مسار إلى مجلد إدخال (تنتهي بشرطة مائلة) يحتوي على ملف/ملفات الحل المُقدَّمة وأي ملف/ملفات أخرى للتمرين. يجب اعتبار هذا المجلد للقراءة فقط. من الممكن تقنيًا الكتابة فيه، لكن من الأفضل استخدام /tmp للملفات المؤقتة (مثل تصريف المصادر).
    • مسار إلى مجلد مخرجات (تنتهي بشرطة مائلة). هذا المجلد قابل للكتابة.
  • يجب أن يكتب السكربت ملف results.json إلى مجلد المخرجات.
  • يجب أن يخرج المشغّل برمز خروج 0 إذا اشتغل بنجاح، بغض النظر عن حالة الاختبارات.

زمن التشغيل المسموح

يحصل مشغّل الاختبارات على 100% من وحدة المعالجة المركزية (CPU) و3 غيغابايت من الذاكرة خلال نافذة مدتها 20 ثانية لكل حل. بعد 20 ثانية، تُوقف العملية ويُبلَّغ عن انتهاء المهلة.

Note

نوصي بشدة باتباع وثيقة أفضل ممارسات الأداء لتقليل احتمال انتهاء المهل.

تنسيق المخرجات

الحقول التالية مدعومة في ملفات results.json:

المستوى الأعلى

الإصدار

المفتاح: version، النوع: number، الوجود: مطلوب

الإصدار: 1، 2، 3

إصدار المواصفة الذي يلتزم به هذا الملف:

  • 1: للمسارات التي لا يستطيع مشغّل اختباراتها تقديم معلومات عن الاختبارات الفردية.
  • 2: للمسارات التي يستطيع مشغّل اختباراتها إخراج معلومات الاختبارات الفردية. الحد الأدنى للإصدار المطلوب للمسارات التي تحتوي على تمارين مفاهيمية.
  • 3: للمسارات التي يستطيع مشغّل اختباراتها ربط الاختبارات الفردية بمهمة.

الحالة

المفتاح: status، النوع: string، الوجود: مطلوب

الإصدار: 1، 2، 3

الحالات الإجمالية التالية صالحة:

  • pass: نجحت جميع الاختبارات.
  • fail: اختبار واحد على الأقل حالته fail أو error.
  • error: لم يُنفَّذ أي اختبار (يعني هذا عادةً خطأ تصريف أو خطأ في الصياغة).

ينبغي استخدام حالة error فقط إذا حدث خطأ في جميع الاختبارات. بالنسبة للغات المصرّفة، يكون هذا عمومًا نتيجة لعدم قدرة الكود على التصريف. بالنسبة للغات المفسّرة، يكون هذا خطأ وقت تشغيل، مثل خطأ في الصياغة يمنع الملف من التحليل.

الرسالة

المفتاح: message، النوع: string، الوجود: مطلوب إذا كانت status = error، أو عندما تكون status = fail وversion = 1

الإصدار: 1، 2، 3

عندما تكون الحالة error (لم يُنفَّذ أي اختبار بشكل صحيح)، ينبغي توفير مفتاح message في المستوى الأعلى. ينبغي أن يقدّم الخطأ الحادث إلى المستخدم. ولأنه المعلومة الوحيدة التي سيتلقاها المستخدم حول كيفية تصحيح مشكلته، يجب أن يكون واضحًا قدر الإمكان:

  • بسّط المسارات لتكون شيئًا مثل <solution-dir>/relative/path بدلًا من /full/path/to، لأن ذلك سيتضمن بيانات ECR خاصة غير مفيدة.
  • عندما يكون ذلك ممكنًا أو مناسبًا، ادمج تتبعات المكدس التي لا تخص كود المستخدم.
  • لا تُظهر أبدًا تتبعات الاستدعاء بدون سياق (أي رسالة الخطأ).
  • لا تغيّر رسالة الخطأ (إن أمكن)، لأن ذلك سيسهّل البحث عن الخطأ.

في Ruby، في حالة خطأ في الصياغة، نقدّم خطأ وقت التشغيل وتتبع المكدس. في اللغات المصرّفة، ينبغي تقديم خطأ التصريف.

قيمة message في المستوى الأعلى محدودة بـ 65535 حرفًا. يكون الطول الأقصى الفعلي أقل إذا كانت القيمة تحتوي على محارف متعددة البايت.

عندما لا تكون الحالة error، إما اضبط القيمة على null أو احذف المفتاح تمامًا.

الاختبارات

المفتاح: tests، النوع: array، الوجود: مطلوب إذا كانت status = fail أو status = pass

الإصدار: 2، 3

هذه مصفوفة نتائج الاختبارات، كما هو موضح في قسم «لكل اختبار» أدناه.

يجب إرجاع الاختبارات بالترتيب المحدد في ملف الاختبارات. بالنسبة للغات التي تنفّذ الاختبارات بترتيب عشوائي، قد يعني هذا إعادة ترتيب النتائج وفقًا للترتيب المحدد في ملف الاختبارات.

السبب في ذلك هو أنه يُعرض للطلاب الفشل الأول فقط، ولذلك من المهم عرض الفشل الصحيح. ولأن الاختبارات تُرتَّب عمومًا في ملف الاختبارات بطريقة التطوير الموجّه بالاختبار (TDD)، ولأن الطلاب في التمارين التطبيقية يرون ملف الاختبارات في المحرر، فإن مواءمة النتائج مع ملف الاختبارات أمر بالغ الأهمية.

لكل اختبار

الاسم

المفتاح: name، النوع: string، الوجود: مطلوب

الإصدار: 2، 3

هذا اسم الاختبار بصيغة مقروءة للبشر.

كود الاختبار

المفتاح: test_code، النوع: string، الوجود: مطلوب إذا كان التمرين تمرينًا مفاهيميًا

الإصدار: 2، 3

يجب وجود هذا في التمارين المفاهيمية وينبغي وجوده في التمارين التطبيقية. يأتي الاختلاف في هذا الشرط من كون الاختبارات لا تُعرض على الطلاب في التمارين المفاهيمية، لذا قد يكون حل التمرين مستحيلًا دون عرض test_code، بينما تُعرض الاختبارات في التمارين التطبيقية.

هذا هو جسم الأمر الذي يجري اختباره. على سبيل المثال، اختبار Ruby التالي:

def test_duplicate_items_uniqs_list
  cart = ShoppingCart.new
  cart.add(:STARIC)
  cart.add(:MEDNEW)
  cart.add(:MEDNEW)
  assert_equal 'Newspaper, Rice', cart.items_list
end

ينبغي أن يُرجع قيمة test_code هي:

"cart = ShoppingCart.new
cart.add(:STARIC)
cart.add(:MEDNEW)
cart.add(:MEDNEW)
assert_equal 'Newspaper, Rice', cart.items_list"

(مع استبدال فواصل الأسطر بـ \n لجعل JSON صالحًا).

الحالة

المفتاح: status، النوع: string، الوجود: مطلوب

الإصدار: 2، 3

حالات الاختبار الفردي التالية صالحة:

  • pass: نجح الاختبار.
  • fail: فشل الاختبار.
  • error: حدث خطأ في الاختبار، أي أنه لم يُرجع قيمة.

الرسالة

المفتاح: message، النوع: string، الوجود: مطلوب إذا كانت status هي fail أو error

الإصدار: 2، 3

يُستخدم المفتاح message الخاص بكل اختبار لإرجاع نتائج اختبار حالته fail أو error. ينبغي أن يكون مقروءًا للبشر قدر الإمكان. أي شيء يُكتب هنا سيُعرض على الطالب عندما لا ينجح اختباره. إذا لم توجد رسالة فشل اختبار أو رسالة خطأ، فإما اضبط القيمة على null أو احذف المفتاح تمامًا. يُسمح أيضًا بإخراج مخرجات مجموعة الاختبارات هنا. قيمة message غير محدودة الطول.

المخرجات

المفتاح: output، النوع: string، الوجود: اختياري

الإصدار: 2، 3

ينبغي استخدام المفتاح output الخاص بكل اختبار لتخزين وإخراج أي شيء يُخرجه المستخدم عمدًا من أجل اختبار.

  • ينبغي إرفاقه بكل نتائج الاختبارات التي تُنتج مخرجات من المستخدم.
  • ينبغي عرض المحتوى الذي يُخرجه المستخدم يدويًا فقط، وليس المخرجات التلقائية من مشغّل الاختبارات.
  • يمكنك إما التقاط المحتوى الذي يُخرَج بالوسائل العادية (مثل puts في Ruby، أو print في Python، أو Debug.WriteLine في C#)، أو يمكنك توفير طريقة يمكن للمستخدم استخدامها (مثل أن يوفر مشغّل اختبارات Ruby للمستخدم طريقة debug متاحة عالميًا يمكنه استخدامها، ولها نفس خصائص طريقة puts القياسية).
  • يجب أن تكون المخرجات محدودة بـ 500 حرف. يُقبل إما الاقتطاع مع رسالة «تم اقتطاع المخرجات. يرجى الاقتصار على 500 حرف» أو إرجاع خطأ في هذه الحالة.

معرّف المهمة

المفتاح: task_id، النوع: number، الوجود: اختياري

الإصدار: 3

اربط اختبارًا بمهمة محددة عبر معرّف المهمة، وهو الرقم المستخدم في بداية عنوان المهمة. لا تربط اختبارًا بمهمة إلا إذا أمكن ربطه بمهمة واحدة تحديدًا.

في الوقت الحالي، لا تحتوي سوى التمارين المفاهيمية على مهام محددة جيدًا يمكنك ربط الاختبارات بها، لكن هذا قد يتغير في المستقبل.

على سبيل المثال، انظر إلى ملف instructions.md التالي:

# Instructions

You're going to write some code to help Lucian cook an exquisite lasagna from his favorite cook book.

## 1. Define the expected oven time in minutes

...

## 2. Calculate the remaining oven time in minutes

...

تحدد هذه التعليمات مهمتين:

  1. تحديد وقت الفرن المتوقع بالدقائق
  2. حساب وقت الفرن المتبقي بالدقائق

يمكن أن يحتوي ملف results.json حينها على مدخل مثل هذا:

{
  "name": "Expected oven time in minutes",
  "status": "pass",
  "task_id": 1,
  "test_code": "Assert.Equal(40, Lasagna.ExpectedMinutesInOven());"
}

أصبح هذا الاختبار الآن مرتبطًا بالمهمة الأولى: «تحديد وقت الفرن المتوقع بالدقائق». لاحظ أن الاسم لا يتعين أن يطابق وصف المهمة.

توجد طرق متعددة يمكن للمسارات تنفيذ ذلك بها:

  • إضافة بيانات وصفية إلى الاختبارات داخل ملف الاختبارات (مثل استخدام السمات/التعليقات التوضيحية/التعليقات) وجعل مشغّل الاختبارات يقرأ هذه البيانات الوصفية عند تشغيل الاختبارات.
  • تخزين ربط اسم الاختبار/معرّف المهمة في ملف منفصل (مثل ملف .meta/config.json الخاص بالتمرين) ودمج هذه المعلومات في ملف results.json المُنشأ.

أمثلة

هذه أمثلة لما يمكن أن يبدو عليه ملف results.json صالح للإصدارات المختلفة:

مثال الإصدار 1

{
  "version": 1,
  "status": "fail",
  "message": "Failed: test_answer\nExpected: 42, actual: 3"
}

مثال الإصدار 2

{
  "version": 2,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()"
    }
  ]
}

مثال الإصدار 3

{
  "version": 3,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()",
      "task_id": 1
    }
  ]
}

اعتبارات واجهة المستخدم/تجربة المستخدم

عند فشل الاختبار

عندما يفشل حل الطالب في اختبار، ينبغي أن يعرض شيئًا مثل:

Test Code:
  <test_code>

Test Result:
  <message>

عند نجاح الاختبار

عندما ينجح الحل في اختبار، ينبغي أن يعرض شيئًا مثل:

Test Code:
  <test_code>

كيفية إضافة بيانات وصفية لمجموعة اختبارات لغتك

كل الطرق تؤدي إلى روما، ولا يوجد نمط محدد لتحقيق ذلك. هناك عدة مقاربات اتبعت حتى الآن:

  • ملفات JSON مساعدة تُجمَّع يدويًا، وتُدمج مع نتائج الاختبارات أثناء وقت تشغيل الاختبار.
  • تحليل ساكن آلي لمجموعة الاختبارات، ويُدمج مع نتائج الاختبارات أثناء وقت تشغيل الاختبار.
    • قد يتم ذلك عن طريق تحليل شجرة التركيب المجردة (AST) أو تحليل النصوص.