منابع آموزشی و پژوهشی، برای دانشگاه و مدرسه
راهنمای انتخابمیز نویسنده
آموزش javascriptدرس ۱۸ از ۲۰رایگان

تست‌نویسی جاوااسکریپت با Node.js؛ آزمون واحد و حالت‌های مرزی

آزمون خوب، قرارداد رفتار برنامه را بررسی می‌کند. در این درس با test runner داخلی Node.js برای تابع درصد پیشرفت تست می‌نویسیم، ورودی‌های مرزی را پوشش می‌دهیم و تفاوت آزمون واحد، یکپارچه و مرورگری را می‌شناسیم.

فضای تمرینبه‌روزرسانی: ۱۴۰۵/۸/۵
کیفیت و پروژهٔ نهایی
در این درس می‌خوانید

دیدن یک خروجی درست در Console نشان نمی‌دهد تابع در همهٔ حالت‌های مهم درست کار می‌کند. آزمون خودکار، انتظار مشخص را قابل تکرار می‌کند تا پس از تغییر کد دوباره همان قرارداد را بررسی کنیم. در درس هجدهم از ابزار داخلی Node.js استفاده می‌کنیم و تابع درصد پیشرفت را می‌آزماییم. هدف، زیادکردن تعداد تست نیست؛ هر تست باید یک رفتار مهم یا خطای محتمل را پوشش دهد.

اول قرارداد تابع را بنویسید

تابع percent دو تعداد صحیحِ امن می‌گیرد: completed باید بین صفر و total باشد و total باید مثبت باشد. خروجی، درصد گرد‌شدهٔ پیشرفت است. برای دادهٔ نامعتبر RangeError می‌دهیم. این قرارداد را پیش از کد تعیین کرده‌ایم؛ آزمون نباید صرفاً همان فرمول پیاده‌سازی را دوباره محاسبه کند. استفاده از .mjs باعث می‌شود نمونه به تنظیم type در package.json وابسته نباشد.

javascript
export function percent(completed, total) {
  if (!Number.isSafeInteger(completed) || !Number.isSafeInteger(total) ||
      total <= 0 || completed < 0 || completed > total) {
    throw new RangeError("Invalid progress counts");
  }
  return Math.round(completed / total * 100);
}
این مثال به محیط مرورگر، پروژه یا Node.js نیاز دارد؛ طبق راهنمای درس اجرا کنید.

کد بالا را در progress.mjs ذخیره کنید. Number.isSafeInteger علاوه بر صحیح‌بودن، محدودهٔ امن نمایش عدد صحیح را بررسی می‌کند. در این مدل، کل صفر معنای قابل محاسبه ندارد؛ اگر محصول شما برای دورهٔ خالی سیاست دیگری دارد، همان قرارداد و تست‌ها باید آگاهانه تغییر کنند. تصمیم قراردادی را با یک مقدار پیش‌فرض بی‌دلیل پنهان نکنید.

فایل آزمون را بسازید

javascript
import test from "node:test";
import assert from "node:assert/strict";
import { percent } from "./progress.mjs";

test("calculates a partial course", () => {
  assert.equal(percent(5, 20), 25);
});

test("supports zero and fully completed progress", () => {
  assert.equal(percent(0, 20), 0);
  assert.equal(percent(20, 20), 100);
});

test("rejects invalid counts", () => {
  for (const [completed, total] of [[1, 0], [-1, 20], [21, 20], [1.5, 20], ["5", 20]]) {
    assert.throws(() => percent(completed, total), RangeError);
  }
});
این مثال به محیط مرورگر، پروژه یا Node.js نیاز دارد؛ طبق راهنمای درس اجرا کنید.

این فایل را progress.test.mjs بنامید. نخستین تست، یک نتیجهٔ معلوم را بررسی می‌کند و تست دوم مرزهای صفر و پایان را پوشش می‌دهد. تست سوم چند ورودی نامعتبر را به همان قرارداد وصل می‌کند. از strict assertion استفاده می‌کنیم تا تبدیل ضمنی نوع، اختلاف را پنهان نکند. نام تست باید بگوید چه رفتار قابل مشاهده‌ای را بررسی می‌کند؛ نامی مانند test1 برای یافتن علت شکست کمک کمی می‌کند.

آزمون را اجرا و نتیجه را بخوانید

shell
node --test progress.test.mjs

باید سه تست موفق ببینید. شکل دقیق گزارش با نسخهٔ Node و reporter می‌تواند فرق کند، اما تعداد شکست‌ها باید صفر و کد خروج موفق باشد. برای دیدن ارزش تست، موقتاً فرمول را طوری تغییر دهید که پیش از ضرب در 100 گرد کند؛ تست درصد جزئی باید شکست بخورد. سپس فرمول صحیح را برگردانید و دوباره اجرا کنید. تستی که هیچ خطای محتملی را نمی‌گیرد، صرفاً تزئین پروژه است.

آزمون واحد، یکپارچه و مرورگری

  • واحد: رفتار یک تابع یا واحد کوچک را با وابستگی کم بررسی می‌کند؛ مانند percent.
  • یکپارچه: همکاری چند بخش را بررسی می‌کند؛ مانند دریافت، اعتبارسنجی و تبدیل داده.
  • مرورگری یا سرتاسری: رفتار کاربر در صفحه را می‌آزماید؛ مانند ارسال فرم با Enter و دیدن عضو تازه.

موفقیت تست percent ثابت نمی‌کند فرم صفحه با صفحه‌کلید کار می‌کند یا درخواست شبکه در حالت 404 درست مدیریت می‌شود. برای رابط، سناریوهای واقعی لازم است؛ ابزارهایی مانند Playwright می‌توانند آن‌ها را خودکار کنند، اما برای پروژهٔ کوچک، یک بازبینی دستیِ مشخص نیز ارزش دارد. وقتی شبکه یا زمان دخیل است، نتیجه نباید به اینترنت زنده یا انتظارهای طولانی و تصادفی وابسته باشد؛ مرز وابستگی را قابل کنترل طراحی کنید.

تست ناهمگام باید پایان کار را واقعاً منتظر بماند

تابع تست می‌تواند async باشد. در آن حالت عملیات Promise را await کنید تا runner پایان و خطا را درست تشخیص دهد. برای انتظار ردشدن Promise، assert.rejects مناسب است و برای throw همگام، assert.throws. فراموشی return یا await می‌تواند تست را پیش از پایان کار تمام کند. همچنین تست‌ها نباید بی‌دلیل به ترتیب اجرای هم وابسته باشند یا یک وضعیت مشترک پنهان را تغییر دهند.

تمرین و پاسخ پیشنهادی

آزمونی برای گردشدن یک‌سوم اضافه کنید. انتظار مشخص برای percent(1, 3) عدد 33 است. سپس دادهٔ NaN و Infinity را نیز در گروه ورودی نامعتبر بررسی کنید. پاسخ زیر یک تست مستقل است؛ آن را به فایل آزمون موجود اضافه کنید. با این کار، تعداد تست‌ها باید چهار شود. برای تمرین فکر کنید چرا مقایسه با همان فرمول تابع در داخل assert، ارزش مستقل کمتری دارد.

javascript
test("rounds a third to an integer percentage", () => {
  assert.equal(percent(1, 3), 33);
  assert.throws(() => percent(NaN, 20), RangeError);
  assert.throws(() => percent(1, Infinity), RangeError);
});

بازبینی یک تغییر واقعی

  • رفتار مورد انتظار را پیش از اصلاح روشن کنید.
  • آزمون مرتبط باید با باگ موجود شکست بخورد و پس از اصلاح موفق شود.
  • حالت مرزی را از دادهٔ واقعی مسئله انتخاب کنید.
  • موفقیت تست واحد را به معنای صحت کل سایت ندانید.
  • پس از موفقیت بررسی‌های مرتبط، بدون دلیل همان آزمون‌ها را بارها تکرار نکنید.

منابع انگلیسی

تابع، قرارداد و آزمون‌ها برای این مسیر نوشته شده‌اند. در درس بعد، امنیت و پاسخ‌گویی رابط را پیش از ساخت پروژهٔ پایانی بررسی می‌کنیم.

پیش‌نیاز و محیط اجرای مثال‌ها

درس 18 از ۲۰؛ پیش‌نیاز: درس‌های قبلی مجموعه. Node.js LTS به‌روز لازم است. فایل‌های progress.mjs و progress.test.mjs را کنار هم بسازید؛ برای این مثال وابستگی npm اضافه لازم نیست.

پیشرفت روی همین مرورگر ذخیره می‌شود.

یکجا انتخاب کن، راحت‌تر دریافت کن

انتخاب‌های من ۰ منبع

مجموعهٔ مطالعه‌ات را بساز.

کنار مقاله‌ها «انتخاب» را بزنید تا چند فایل را با هم دریافت کنید.

انتخاب مقاله‌های رایگان