مولدهای Test


مولد تست یک نرم‌افزار مخصوص هر ترک است که تست‌های یک تمرین عملی را به‌طور خودکار تولید می‌کند. این کار را با تبدیل موارد تست JSON تمرین به تست‌هایی به زبان همان ترک انجام می‌دهد.

مزایا

داشتن یک مولد تست چند مزیت دارد:

  1. تمرین‌ها سریع‌تر اضافه می‌شوند
  2. بخش‌های خسته‌کننده‌ی افزودن یک تمرین را خودکار می‌کند
  3. همگام‌سازی تست‌ها با آخرین داده‌ی متعارف آسان است

موارد استفاده

به‌طور کلی، مولد تست را برای یکی از این دو کار اجرا می‌کنند:

  1. تولید تست‌های یک تمرین جدید
  2. به‌روزرسانی تست‌های یک تمرین موجود

تولید تست برای تمرین جدید

افزودن یک مولد تست برای یک تمرین جدید به شما امکان می‌دهد فایل(های) تست آن را تولید کنید. به شرطی که خود مولد تست از قبل پیاده‌سازی شده باشد، تولید تست‌های تمرین جدید (بسیار) کم‌زحمت‌تر از نوشتن آن‌ها از صفر است.

به‌روزرسانی تست‌های تمرین موجود

وقتی تمرینی مولد تست داشته باشد، می‌توانید آن را دوباره اجرا کنید تا تمرین را با آخرین داده‌ی متعارفش به‌روزرسانی یا همگام کنید. توصیه می‌کنیم این کار را به‌صورت دوره‌ای انجام دهید تا ببینید آیا موارد تست مشکل‌داری هست که باید به‌روزرسانی شوند یا تست‌های جدیدی هست که ممکن است بخواهید اضافه کنید.

نقطه‌ی شروع

هنگام پیاده‌سازی مولد تست برای یک تمرین، دو نقطه‌ی شروع ممکن وجود دارد:

  1. تمرین جدید است و در نتیجه هیچ تستی ندارد
  2. تمرین از قبل وجود دارد و در نتیجه تست‌های موجودی دارد
Caution

اگر تست‌های موجودی وجود دارد، مولد تست را طوری پیاده‌سازی کنید که تست‌هایی که تولید می‌کند، راه‌حل‌های موجود را خراب نکند.

طراحی

به‌طور کلی، فایل‌های تست به یکی از این دو روش تولید می‌شوند:

  • کد: فایل‌های تست (بیشتر) از طریق کد تولید می‌شوند
  • قالب‌ها: فایل‌های تست (بیشتر) با استفاده از قالب‌ها تولید می‌شوند

ما دیده‌ایم که رویکرد مبتنی بر کد به کد مولد تست نسبتاً پیچیده‌ای منجر می‌شود، در حالی که رویکرد مبتنی بر قالب ساده‌تر است.

روشی که توصیه می‌کنیم این جریان است:

  1. داده‌ی متعارف تمرین را بخوانید
  2. موارد تستی را که در فایل tests.toml تمرین با include = false علامت خورده‌اند، کنار بگذارید
  3. داده‌ی متعارف تمرین را به شکلی تبدیل کنید که بتوان از آن در یک قالب استفاده کرد
  4. داده‌ی متعارف تمرین را به قالبی مخصوص همان تمرین بدهید

مزیت اصلی این ساختار این است که هر تمرین قالب خودش را دارد، که:

  • روشن می‌کند فایل‌های تست چگونه تولید می‌شوند
  • دیباگ کردن آن‌ها را آسان‌تر می‌کند
  • ویرایش آن‌ها را بدون خطر خراب کردن تمرین دیگر، ایمن می‌کند
Caution

هنگام طراحی مولد تست، بکوشید:

  • پیش‌پردازش داده‌ی متعارف را درون مولد تست به حداقل برسانید
  • وابستگی میان قالب‌ها را کم کنید

پیاده‌سازی

مولد تست معمولاً (بیشتر) به زبان همان ترک نوشته می‌شود.

Caution

هرچند آزادید از زبان‌های دیگر استفاده کنید، هر زبان اضافه‌ای نگهداری یا مشارکت در ترک را دشوارتر می‌کند. بنابراین توصیه می‌کنیم تا جای ممکن از زبان خود ترک استفاده کنید، چون نگهداری و مشارکت را آسان‌تر می‌کند.

قالب‌بندی

اگر ترک شما ابزاری برای قالب‌بندی کد دارد، بهتر است آن را به‌عنوان مرحله‌ی پس‌پردازش، بعد از رندر کردن قالب اجرا کنید.

داده‌ی متعارف

داده‌ی اصلی‌ای که مولد تست با آن کار می‌کند، فایل canonical-data.json تمرین است. این فایل در مخزن exercism/problem-specifications تعریف شده است، که فراداده‌ی مشترک بسیاری از تمرین‌های Exercism را تعریف می‌کند.

Caution

همه‌ی تمرین‌ها فایل canonical-data.json ندارند! اگر نداشته باشند، باید تست‌ها را دستی بسازید، چون داده‌ای برای کار مولد تست وجود ندارد.

ساختار

داده‌ی متعارف در یک شیء JSON تعریف می‌شود. این شیء فیلدی به اسم "cases" دارد که موارد تست را در خود نگه می‌دارد. این موارد تست (معمولاً) یک‌به‌یک با تست‌های ترک شما متناظرند.

هر مورد تست چند ویژگی دارد که مهم‌ترین‌شان توضیح، ویژگی، مقدار(های) ورودی و مقدار انتظاری است. اینجا یک نمونه‌ی (ناقص) از فایل canonical-data.json تمرین leap را می‌بینید:

{
  "exercise": "leap",
  "cases": [
    {
      "uuid": "6466b30d-519c-438e-935d-388224ab5223",
      "description": "year not divisible by 4 in common year",
      "property": "leapYear",
      "input": {
        "year": 2015
      },
      "expected": false
    },
    {
      "uuid": "4fe9b84c-8e65-489e-970b-856d60b8b78e",
      "description": "year divisible by 4, not divisible by 100 in leap year",
      "property": "leapYear",
      "input": {
        "year": 1996
      },
      "expected": true
    }
  ]
}

مسئولیت اصلی مولد تست تبدیل این داده‌ی JSON به تست‌های مخصوص ترک است. در ادامه می‌بینید داده‌ی JSON بالا چگونه می‌تواند به کد تست Nim ترجمه شود:

import unittest
import leap

suite "Leap":
  test "year not divisible by 4 in common year":
    check isLeapYear(2015) == false

  test "year divisible by 4, not divisible by 100 in leap year":
    check isLeapYear(1996) == true

ساختار فایل canonical-data.json به‌خوبی مستند شده است و تعریف اسکیمای JSON هم دارد.

تودرتویی

برخی تمرین‌ها در داده‌ی متعارفشان از تودرتویی استفاده می‌کنند. یعنی هر عنصر در آرایه‌ی cases می‌تواند یکی از این دو باشد:

  1. یک مورد تست معمولی (بدون مورد تست فرزند)
  2. یک گروه از موارد تست (یک یا چند مورد تست فرزند)
Note

می‌توانید نوع یک عنصر را با بررسی وجود فیلدهایی که مخصوص یک نوع عنصر هستند، تشخیص دهید. احتمالاً بهترین راه برای این کار استفاده از کلید "cases" است که فقط در گروه‌های موارد تست وجود دارد.

اینجا نمونه‌ای از موارد تست تودرتو را می‌بینید:

{
  "cases": [
    {
      "uuid": "e9c93a78-c536-4750-a336-94583d23fafa",
      "description": "data is retained",
      "property": "data",
      "input": {
        "treeData": ["4"]
      },
      "expected": {
        "data": "4",
        "left": null,
        "right": null
      }
    },
    {
      "description": "insert data at proper node",
      "cases": [
        {
          "uuid": "7a95c9e8-69f6-476a-b0c4-4170cb3f7c91",
          "description": "smaller number at left node",
          "property": "data",
          "input": {
            "treeData": ["4", "2"]
          },
          "expected": {
            "data": "4",
            "left": {
              "data": "2",
              "left": null,
              "right": null
            },
            "right": null
          }
        }
      ]
    }
  ]
}
Caution

اگر ترک شما از گروه‌بندی تست‌ها پشتیبانی نمی‌کند، باید:

  • سلسله‌مراتب cases را پیمایش/مسطح کنید تا فقط داخلی‌ترین موارد تست (برگ‌ها) باقی بماند
  • توضیح مورد تست را با توضیح(های) والدش ترکیب کنید تا یک اسم تست یکتا بسازید

مقادیر ورودی و انتظاری

محتوای کلیدهای input و expected در موارد تست بسیار متنوع است. در بیشتر موارد، مقادیر اسکالر (مثل عدد، مقدار منطقی یا رشته) یا اشیاء ساده خواهند بود. با این حال، گاهی مقادیر پیچیده‌تری هم می‌بینید که احتمالاً کمی پیش‌پردازش لازم دارند، مثل لامبداها در شبه‌کد، فهرست عملیاتی که باید روی کد دانشجو انجام شود، و از این قبیل.

سناریوها

موارد تست یک فیلد اختیاری به اسم scenarios دارند. مولد تست می‌تواند از این فیلد برای اعمال رفتار ویژه روی برخی موارد تست استفاده کند. رایج‌ترین کاربردش نادیده گرفتن انواع خاصی از تست‌هاست، مثلاً تست‌هایی با سناریوی "unicode"، چون ممکن است زبان ترک شما از یونیکد پشتیبانی نکند.

فهرست کامل سناریوها را اینجا می‌بینید.

خواندن فایل‌های canonical-data.json

چند گزینه برای خواندن فایل‌های canonical-data.json وجود دارد:

  1. آن‌ها را مستقیم از مخزن problem-specifications بگیرید (مثلاً https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/leap/canonical-data.json).
  2. مخزن problem-specifications را به‌عنوان ساب‌ماژول گیت به مخزن ترک اضافه کنید.
  3. آن‌ها را از کش configlet بخوانید. مکانش به سیستم کاربر بستگی دارد، اما می‌توانید با configlet info -o -v d | head -1 | cut -d " " -f 5 مکانش را به‌صورت برنامه‌ای به دست آورید.

موارد تست مخصوص ترک

اگر ترک شما می‌خواهد چند مورد تست اضافه‌ی مخصوص خودش (که در داده‌ی متعارف پیدا نمی‌شوند) اضافه کند، یکی از گزینه‌ها ساختن فایلی به اسم additional-test-cases.json است؛ مولد تست می‌تواند آن را پیش از سپردن داده به قالب برای رندر، با فایل canonical-data.json ادغام کند.

قالب‌ها

موتور قالبی که استفاده می‌کنید احتمالاً مخصوص ترک خواهد بود. در حالت ایده‌آل می‌خواهید قالب‌هایتان تا حد ممکن ساده و سرراست باشند، پس نگران تکرار کد و از این قبیل نباشید.

قالب‌ها داده‌شان را از مولد تست می‌گیرند و برای رندر کردن، روی آن پیمایش می‌کنند.

Note

برای اینکه قالب‌ها ساده بمانند، ممکن است مفید باشد کمی پیش‌پردازش در سمت مولد تست انجام دهید، یا چند «فیلتر» یا هر سازوکار گسترشی که قالب‌هایتان در اختیار می‌گذارد تعریف کنید.

استفاده از configlet

configlet ابزار اصلی نگهداری ترک است و می‌توان از آن برای این کارها استفاده کرد:

  • ساختن فایل‌های یک تمرین جدید: bin/configlet create --practice-exercise <slug> را اجرا کنید
  • همگام‌سازی فایل tests.toml یک تمرین موجود: bin/configlet sync --tests --update --exercise <slug> را اجرا کنید
  • گرفتن داده‌ی متعارف تمرین و ذخیره روی دیسک (این کار اثر جانبی هر یک از دو دستور بالاست)

همین configlet را به ابزاری عالی برای ترکیب با مولد تست و ساختن جریان‌های کاری واقعاً قدرتمند تبدیل می‌کند.

رابط خط فرمان

می‌خواهید استفاده از مولد تست هم آسان و هم قدرتمند باشد. برای همین، توصیه می‌کنیم یک یا چند فایل اسکریپت بسازید.

Note

آزادید هر قالب فایل اسکریپتی را که بهترین تناسب را با ترک شما دارد انتخاب کنید. اسکریپت‌های شل و اسکریپت‌های PowerShell دو گزینه‌ی رایج‌اند که هر دو خوب کار می‌کنند.

اینجا نمونه‌ای از یک اسکریپت شل را می‌بینید که configlet و یک مولد تست را ترکیب می‌کند تا اسکلت یک تمرین جدید را به‌سرعت بسازد:

bin/fetch-configlet
bin/configlet create --practice-exercise <slug>
path/to/test-generator <slug>

ساختن از صفر

پیش از آنکه ساختن مولد تست را شروع کنید، پیشنهاد می‌کنیم نگاهی به چند مولد تست موجود بیندازید تا ببینید ترک‌های دیگر آن‌ها را چطور پیاده‌سازی کرده‌اند:

اگر سؤالی دارید، انجمن بهترین جا برای پرسیدن آن‌هاست. بحث‌های انجمن درباره‌ی مولد تست Rust و مولد تست JavaScript هم ممکن است کمک‌کننده باشد.

محصول حداقلی قابل ارائه

توصیه می‌کنیم مولد تست را به‌صورت تدریجی بسازید و از یک محصول حداقلی قابل ارائه شروع کنید. نسخه‌ی حداقلی صرفاً فایل canonical-data.json یک تمرین را می‌خواند و همان داده را به قالب می‌دهد.

با تمرکز روی یک تمرین شروع کنید، ترجیحاً تمرینی ساده مثل leap. فقط وقتی آن یک تمرین جواب داد، به‌تدریج تمرین‌های بیشتری اضافه کنید.

و بکوشید مولد تست را تا حد امکان ساده نگه دارید.

Note

در حالت ایده‌آل، یک مشارکت‌کننده می‌تواند بدون آنکه لازم باشد بفهمد مولد تست در درون چطور کار می‌کند، فقط یک قالب موجود را بچسباند یا تغییر دهد.

استفاده یا مشارکت

نحوه‌ی استفاده از یک مولد تست یا مشارکت در آن، به هر ترک بستگی دارد. دستورالعمل‌ها را در README.md و CONTRIBUTING.md ترک یا پوشه‌ی کد مولد تست بجویید.