رابط اجراکننده‌ی Test


اجراکننده‌های تست یک مسئولیت واحد دارند: گرفتن یک راه‌حل، اجرای همه‌ی تست‌ها و برگرداندن یک خروجی استاندارد. همه‌ی تعامل‌ها با وب‌سایت Exercism به‌صورت خودکار انجام می‌شود و بخشی از این مشخصات نیست.

اجرا

  • یک اجراکننده‌ی تست باید یک اسکریپت اجرایی ارائه دهد. اطلاعات بیشتر را می‌توانید در فایل docker.md پیدا کنید.
  • اسکریپت سه پارامتر دریافت می‌کند:
    • «نامک» تمرین (مثلاً two-fer).
    • مسیری به یک پوشه‌ی ورودی (با اسلش در انتها) که شامل فایل(های) راه‌حل ارسال‌شده و هر فایل(های) دیگر تمرین است. این پوشه باید فقط‌خواندنی در نظر گرفته شود. از نظر فنی نوشتن در آن ممکن است، اما بهتر است برای فایل‌های موقت (مثلاً برای کامپایل کردن سورس‌ها) از /tmp استفاده کنید.
    • مسیری به یک پوشه‌ی خروجی (با اسلش در انتها). این پوشه قابل نوشتن است.
  • اسکریپت باید یک فایل results.json در پوشه‌ی خروجی بنویسد.
  • اجراکننده، اگر با موفقیت اجرا شده باشد، باید با کد خروجی 0 بسته شود، بدون توجه به وضعیت تست‌ها.

زمان اجرای مجاز

اجراکننده‌ی تست ۱۰۰٪ از پردازنده و ۳ گیگابایت حافظه را برای یک بازه‌ی ۲۰ ثانیه‌ای به ازای هر راه‌حل در اختیار می‌گیرد. پس از ۲۰ ثانیه، فرایند متوقف می‌شود و وقفه‌ی زمانی گزارش می‌کند.

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 در سطح بالا به ۶۵۵۳۵ کاراکتر محدود است. اگر مقدار شامل کاراکترهای چندبایتی باشد، بیشترین طول مؤثر کمتر است.

وقتی وضعیت 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 در هر تست برای برگرداندن نتایج تستی با status برابر fail یا error استفاده می‌شود. باید تا حد امکان برای انسان خوانا باشد. هر چیزی که اینجا نوشته شود، وقتی تست دانش‌آموز قبول نشود به او نمایش داده می‌شود. اگر پیام شکست یا پیام خطایی وجود ندارد، یا مقدار را null بگذارید یا کلید را کاملاً حذف کنید. همچنین مجاز است که خروجی مجموعه‌ی تست را اینجا قرار دهید. مقدار message محدودیتی در طول ندارد.

خروجی

کلید: output، نوع: string، حضور: اختیاری

نسخه: 2، 3

از کلید output در هر تست باید برای ذخیره و نمایش هر چیزی استفاده شود که کاربر به‌طور عمدی برای یک تست خروجی می‌دهد.

  • باید به همه‌ی نتایج تستی که خروجی کاربر را تولید می‌کنند ضمیمه شود.
  • فقط محتوایی که کاربر به‌صورت دستی خروجی داده است باید نمایش داده شود، نه خروجی خودکار اجراکننده‌ی تست.
  • می‌توانید محتوایی را که از راه‌های معمول خروجی داده می‌شود ثبت کنید (مثلاً puts در Ruby، print در Python یا Debug.WriteLine در C#)، یا متدی در اختیار کاربر بگذارید (مثلاً اجراکننده‌ی تست Ruby متد debug را با دسترسی سراسری در اختیار کاربر قرار می‌دهد تا از آن استفاده کند، که ویژگی‌هایی مانند متد استاندارد puts دارد).
  • خروجی باید به ۵۰۰ کاراکتر محدود شود. یا کوتاه کردن آن با پیامی به شکل «Output was truncated. Please limit to 500 chars» یا برگرداندن خطا در این وضعیت، هر دو قابل قبول‌اند.

شناسه‌ی وظیفه

کلید: 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

...

این دستورها دو وظیفه تعریف کرده‌اند:

۱. زمان مورد انتظار پخت در فر را بر حسب دقیقه تعریف کنید ۲. زمان باقی‌مانده‌ی پخت در فر را بر حسب دقیقه محاسبه کنید

فایل 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 برای نسخه‌های مختلف است:

نمونه‌ی v1

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

نمونه‌ی v2

{
  "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()"
    }
  ]
}

نمونه‌ی v3

{
  "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 یا تجزیه‌ی متنی انجام شود