۱۸. آزمون واحد کاربردی؛ قرارداد داده و قوانین تغییرناپذیر
در سطح متوسط، اجرای بدون خطا کافی نیست. باید نشان بدهید تغییر یک بخش، رفتار قبلی بخشهای دیگر را خراب نکرده است. آزمون واحد را از قانون کسبوکار آغاز میکنیم: تعداد سبد از موجودی بیشتر نمیشود، قیمت از کاتالوگ میآید و ویرایش یک ردیف، ورودی قبلی را دستکاری نمیکند. فایلهای مدل عمداً از DOM جدا هستند تا برای چنین بررسیهایی مجبور به ساخت مرورگر مصنوعی نباشیم.
یک آزمون، یک رفتار روشن
نمونهٔ زیر را در ریشهٔ بسته با نام tests/cart-extra.test.mjs ذخیره و با node --test tests/cart-extra.test.mjs اجرا کنید. import نسبی دقیقاً به مدل موجود پروژه اشاره میکند. مسیر درست و ورودی مشخصاند؛ به تاریخ جاری، اینترنت یا دادهٔ شخصی وابسته نیستیم.
import test from 'node:test';
import assert from 'node:assert/strict';
import { setQuantity, cartTotal } from '../projects/shop/model.mjs';
const catalog = [{
id: 'js-book', name: 'کتاب تمرین', category: 'کتاب',
price: 120000, stock: 2
}];
test('cart uses catalog price and keeps original input', () => {
const before = [];
const after = setQuantity(before, catalog, 'js-book', 2);
assert.deepEqual(before, []);
assert.deepEqual(after, [{ id: 'js-book', quantity: 2 }]);
assert.equal(cartTotal(after, catalog), 240000);
});این آزمون سه مشاهدهٔ مرتبط با یک عملیات دارد؛ تعداد assertion بهتنهایی نشانهٔ کیفیت نیست. اگر قیمت را داخل ردیف سبد ذخیره کنیم و مدل ناخواسته به آن اعتماد کند، آزمون جدا برای این مرز لازم میشود. در طراحی فعلی cart فقط شناسه و تعداد دارد و cartTotal مبلغ را از catalog پیدا میکند. این انتخاب برای مثال مرورگری مفید است، اما پرداخت واقعی همچنان باید محاسبهٔ معتبر سمت سرور داشته باشد.
مرزها را به جدول تبدیل کنید
آزمون «تعداد یک درست است» خطای اعشاری یا موجودی صفر را پیدا نمیکند. برای قانون تعداد، مجموعهای از حالتها تعریف کنید. این کد را به همان فایل اضافه کنید؛ هر حالت نام مستقل دارد و شکست آن قابل تشخیص است.
for (const quantity of [-1, 1.5, 3, NaN]) {
test(`rejects invalid quantity ${quantity}`, () => {
assert.throws(() => setQuantity([], catalog, 'js-book', quantity));
});
}
test('zero removes an existing row', () => {
const result = setQuantity(
[{ id: 'js-book', quantity: 1 }], catalog, 'js-book', 0
);
assert.deepEqual(result, []);
});در assert.throws باید تابعی بدهیم که هنگام آزمون اجرا شود. اگر همان ابتدا تابع مدل را صدا بزنیم، خطا پیش از assertion رخ میدهد و هدف آزمون مبهم میشود. برای Promise باید assert.rejects یا await مناسب داشته باشیم؛ assert.throws ردشدن Promise را بررسی نمیکند. عبارت «هیچ خطایی نداد» نیز قرارداد خروجی را اثبات نمیکند؛ مقدار، نوع، اثر جانبی و مرز داده را مشخص کنید.
آزمون را عمداً حساس کنید
در یک کپی آزمایشی، شرط quantity>stock را موقتاً بردارید و آزمون تعداد ۳ را اجرا کنید. باید شکست بخورد. سپس تغییر را برگردانید. این کار کوچک نشان میدهد آزمون واقعاً قانون موجودی را میسنجد. آزمونی که بعد از حذف قانون هم سبز میماند، احتمالاً فقط کد را اجرا کرده است. نتیجهٔ شکست عمدی را در نسخهٔ نهایی پروژه نگه ندارید؛ آن را در گزارش تمرین توضیح بدهید.
از کپیکردن فرمول پیادهسازی در expected پرهیز کنید. اگر expected را با همان تابع مورد آزمون بسازید، احتمال دارد دو طرف یک اشتباه را تکرار کنند. مبلغ ۲۴۰۰۰۰ را از صورت مسئله میدانیم. برای تابع total نیز نمونهٔ کوچک با جمع دستی، مرز عدد صحیح امن و رد ورودی نامعتبر لازم است. نام آزمون باید به رفتار اشاره کند؛ نامی مثل test1 در زمان نگهداری کمک نمیکند.
آزمون با شبکه و فایل چه فرقی دارد؟
آزمون واحد مدل سبد یا تبدیل مبلغ، سرور را بالا نمیآورد. آزمون یکپارچه در درس بیستودوم اتصال HTTP، نشست، مالکیت و ذخیره را با هم بررسی میکند. سبزبودن مدل نمیگوید رابط پس از حذف ردیف فوکوس درستی دارد. همچنین سبزبودن API تضمین نمیکند خطای آن برای کاربر خواناست. هر سطح، پرسش مشخصی را پاسخ میدهد و جای سطح دیگر را نمیگیرد.
تمرین، پاسخ راهنما و معیار تحویل
برای parseAmount در projects/expenses/model.mjs یک فایل آزمون مستقل بنویسید: رقم فارسی و عربی، فاصلهٔ اطراف، صفر، مقدار اعشاری، جداکننده و سقف مجاز را پوشش بدهید. پاسخ راهنما: «۱۲۳» و «١٢٣» هر دو 123 میشوند؛ ورودی «1,000» طبق قرارداد این پروژه رد میشود، نه اینکه خودسرانه جداکنندهاش حذف شود. هر آزمون باید ورودی صریح و خروجی یا خطای مورد انتظار داشته باشد.
معیار تحویل، داشتن نامهای روشن، استقلال آزمونها و شکست حداقل یک آزمون در برابر تغییر عمدی یک قانون است. ابتدا npm test را برای آزمونهای همراه اجرا کنید، سپس فایل تازه را جدا اجرا و نتیجه را ثبت کنید. شمارهٔ نسخهٔ Node را هم بنویسید. اگر فقط خروجی سبز را کپی کردهاید و نمیتوانید توضیح دهید کدام خطای واقعی پیدا میشود، بازبینی لازم است.
منابع رسمی برای مطالعهٔ بیشتر
https://nodejs.org/docs/latest-v24.x/api/test.html
https://nodejs.org/docs/latest-v24.x/api/assert.html
تألیف و مثالهای اختصاصی: تحریریه درسآوند.