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

تست نرم‌افزار: راهنمای شروع برای تیم‌هایی که هنوز تست ندارند
در این مقاله می‌خوانید
  1. تست واقعاً چه چیزی را حل می‌کند
  2. سه لایهٔ تست
  3. تست واحد
  4. تست یکپارچگی
  5. تست انتها به انتها
  6. از کجا شروع کنیم
  7. تست شکننده: بزرگ‌ترین دشمن
  8. ابزار را چطور انتخاب کنیم
  9. Test Coverage: عددی که فریب می‌دهد
  10. چه چیزی را تست نکنیم
  11. تست جای رصد را نمی‌گیرد
  12. جمع‌بندی
  13. منابع و مطالعهٔ بیشتر

بیشتر مقالات تست نرم‌افزار برای تیم‌هایی نوشته شده‌اند که از قبل تست دارند. این یکی برای شماست اگر هنوز ندارید و هر بار که موضوع مطرح می‌شود، به این نتیجه می‌رسید که «الان وقتش نیست».

خبر خوب: لازم نیست از صفر به صد بروید. تیم‌هایی که موفق می‌شوند تست را وارد کارشان کنند، تقریباً همیشه از یک نقطهٔ کوچک و مشخص شروع کرده‌اند.

تست واقعاً چه چیزی را حل می‌کند

سوءتفاهم رایج این است که تست برای «پیدا کردن باگ» است. تست باگ‌های موجود را کمتر پیدا می‌کند؛ کار اصلی‌اش این است که مانع برگشتن باگ‌هایی شود که یک بار حل کرده‌اید.

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

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

سه لایهٔ تست

تست واحد

یک تابع را جدا از بقیه امتحان می‌کند. سریع است — هزاران‌تایش در چند ثانیه اجرا می‌شود — و وقتی شکست می‌خورد دقیقاً می‌گوید کجا.

test('تخفیف بیشتر از قیمت نمی‌شود', () => {
  expect(applyDiscount(1000, 150)).toBe(0);
});

مناسب برای منطق محاسباتی: قیمت‌گذاری، اعتبارسنجی، تبدیل تاریخ، قالب‌بندی.

تست یکپارچگی

چند بخش را با هم امتحان می‌کند — مثلاً یک endpoint به‌همراه دیتابیس. کندتر است ولی چیزی را می‌گیرد که تست واحد نمی‌تواند: فرضیات ناسازگار بین دو ماژول.

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

تست انتها به انتها

مرورگر واقعی را باز می‌کند و مسیر کاربر را طی می‌کند. کندترین و شکننده‌ترین نوع، ولی تنها نوعی که می‌گوید «کاربر واقعاً می‌تواند خرید کند».

await page.goto('/checkout');
await page.fill('#card', '1234...');
await page.click('text=پرداخت');
await expect(page.locator('.success')).toBeVisible();

از کجا شروع کنیم

توصیهٔ متعارف این است که با تست واحد شروع کنید. برای تیمی که هیچ تستی ندارد، معمولاً توصیهٔ اشتباهی است — چون بازگشت سرمایه‌اش دیر معلوم می‌شود و انگیزه از بین می‌رود.

مسیری که در عمل جواب می‌دهد:

گام ۱ — یک تست انتها به انتها برای مهم‌ترین مسیر. همان مسیری که اگر بشکند، کسب‌وکار متوقف می‌شود: ثبت‌نام، ورود، یا خرید. یک تست. همین یک تست، بیشتر از پنجاه تست واحد برای توابع کم‌اهمیت ارزش دارد.

گام ۲ — برای هر باگی که رفع می‌کنید، یک تست بنویسید. این ساده‌ترین قاعدهٔ ممکن است و بهترین نتیجه را می‌دهد. مجموعهٔ تست شما به‌طور طبیعی حول همان جاهایی رشد می‌کند که واقعاً می‌شکنند — نه جایی که حدس زده‌اید ممکن است بشکند. این همان مرحلهٔ آخر چرخهٔ عمر یک باگ است.

گام ۳ — تست واحد برای منطق پیچیده. هر جا محاسبه‌ای هست که اشتباهش پرهزینه است.

گام ۴ — اجرای خودکار. تستی که دستی اجرا می‌شود، به‌مرور اجرا نمی‌شود. وقتی روی هر تغییر خودکار اجرا شد، تازه ارزش واقعی‌اش شروع می‌شود.

تست شکننده: بزرگ‌ترین دشمن

تست شکننده تستی است که گاهی می‌گذرد و گاهی نه، بدون آنکه کدی عوض شده باشد. این‌ها خطرناک‌ترین چیزی هستند که می‌تواند بر سر مجموعهٔ تست شما بیاید — چون تیم را به نادیده گرفتن شکست‌ها عادت می‌دهند، و آن‌وقت تست واقعی هم که بشکند کسی جدی نمی‌گیرد.

علت‌های رایج و راه‌حلشان:

  • انتظار با زمان ثابت. sleep(2000) روی سیستم سریع اضافی است و روی CI کند کافی نیست. به‌جایش منتظر شرط بمانید، نه زمان: تا وقتی فلان عنصر ظاهر شود.
  • وابستگی به ترتیب اجرا. تستی که فقط بعد از تست دیگری کار می‌کند. هر تست باید حالت خودش را بسازد و تمیز کند.
  • تاریخ و زمان واقعی. تستی که با new Date() کار می‌کند، روزی در نیمه‌شب یا در سال کبیسه می‌شکند. زمان را در تست ثابت کنید.
  • دادهٔ مشترک بین تست‌ها. دو تست که یک رکورد دیتابیس را دستکاری می‌کنند، در اجرای موازی به هم می‌خورند.

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

ابزار را چطور انتخاب کنیم

این سؤال معمولاً بیشتر از آنچه ارزشش را دارد وقت می‌گیرد. چند اصل که کوتاهش می‌کند:

چیزی را انتخاب کنید که اکوسیستمتان پیش‌فرض کرده. اگر پروژه‌تان با Vite ساخته شده، Vitest؛ اگر React با ابزار متعارف است، Jest و Testing Library. جنگیدن با پیش‌فرض‌ها هزینه‌ای است که چیزی برنمی‌گرداند.

برای تست انتها به انتها، Playwright انتخاب امن امروزی است. چند مرورگر را پشتیبانی می‌کند، انتظار هوشمند دارد که بخش بزرگی از شکنندگی را حذف می‌کند، و ابزار خوبی برای دیدن اینکه تست چرا شکست دارد.

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

Test Coverage: عددی که فریب می‌دهد

پوشش تست می‌گوید چند درصد خطوط کد در جریان تست اجرا شده‌اند. این با «چند درصد کد درست تست شده» یکی نیست:

test('کار می‌کند', () => {
  calculateTotal(items);      // اجرا شد، ولی هیچ چیزی بررسی نشد
});

این تست پوشش می‌سازد و هیچ ارزشی ندارد. هدف‌گذاری روی عدد پوشش، تیم را به سمت نوشتن همین تست‌ها هل می‌دهد.

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

چه چیزی را تست نکنیم

تست هزینهٔ نگهداری دارد. تستی که مدام می‌شکند بدون اینکه باگی باشد، بدتر از نداشتن تست است — چون تیم یاد می‌گیرد شکست تست را نادیده بگیرد.

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

تست جای رصد را نمی‌گیرد

نکته‌ای که تیم‌های تازه‌کار در تست معمولاً دیر می‌فهمند: هیچ مجموعهٔ تستی نمی‌تواند شرایط production را بازسازی کند.

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

پس این دو مکمل هم‌اند: تست جلوی برگشتن باگ‌های شناخته‌شده را می‌گیرد؛ رصد خطا باگ‌های ناشناخته را نشان می‌دهد. تیمی که فقط تست دارد، از باگ‌هایی که هرگز به فکرش نرسیده بی‌خبر می‌ماند.

اگر می‌خواهید ببینید رصد خطا در عمل چه چیزی را پوشش می‌دهد که تست نمی‌تواند، مقالهٔ رصد خطای وب‌سایت: چرا لاگ سرور کافی نیست در وبلاگ ژابیز پردا این تفاوت را با مثال‌های سامانه‌های سازمانی توضیح داده است.

جمع‌بندی

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

ظرف چند ماه مجموعه‌ای خواهید داشت که دقیقاً از جاهایی محافظت می‌کند که واقعاً می‌شکنند — و این از هر مجموعهٔ بزرگی که از روی حدس ساخته شده باشد، ارزشمندتر است.

منابع و مطالعهٔ بیشتر

باگ‌ماگ را رایگان امتحان کنید

خطاهای سایتتان را خودکار ثبت کنید و بگذارید کاربران با یک کلیک باگ گزارش دهند.

شروع رایگان