انواع تست نرمافزار؛ از یونیت تست تا exploratory testing
حدود ۶ دقیقه مطالعه
خیلی وقتها وقتی حرف از «تست نوشتن» میشه، ذهن آدم فقط میره سراغ یونیت تست — یه تابع رو صدا بزن، خروجیش رو با یه مقدار مورد انتظار مقایسه کن، تمام. ولی واقعیت اینه که «تست» یه چیز یکدست نیست؛ یه طیف کامل از سطحهای مختلفه، هرکدوم با هزینه، سرعت و هدف متفاوت. اگه بخوای یه استراتژی تست معقول برای پروژهت بچینی، اول باید بدونی این سطحها دقیقاً چه فرقی با هم دارن.
اول از همه، manual در برابر automated
قبل از رفتن سراغ انواع تست، یه تمایز پایهای مهمه: تست دستی و تست خودکار. توی تست دستی، یه آدم واقعی میشینه و با اپلیکیشن یا API تعامل میکنه — کلیک میکنه، فرم پر میکنه، نتیجه رو چک میکنه. این روش هم گرونتره (چون نیاز به وقت یه انسان داره) و هم مستعد خطاست، چون ممکنه یه قدم فراموش بشه یا یه چیزی اشتباه تایپ بشه.
تست خودکار برعکس، توسط یه اسکریپت از پیش نوشتهشده اجرا میشه — از یه تابع ساده گرفته تا یه سناریوی چندمرحلهای پیچیده توی UI. این روش قابلاعتمادتر و تکرارپذیرتره، ولی کیفیتش کاملاً به کیفیت خود تستهایی که نوشتی بستگی داره. تست خودکار بخش جداییناپذیر continuous integration و continuous delivery هست و راهیه که QA رو با رشد پروژه scale میکنی — ولی همونطور که میگم، این به معنی بینیازی کامل از تست دستی نیست؛ در ادامه به exploratory testing هم میرسیم که هنوز جای خودشو داره.
سطحهای مختلف تست خودکار
یونیت تست
یونیت تست پایینترین سطح و نزدیکترین تست به کد خام است. اینجا تکتک متدها و توابع رو، جدا از بقیهی سیستم، تست میکنی. مثلاً یه تابع که قیمت رو با تخفیف محاسبه میکنه، بدون اینکه به دیتابیس یا API خارجی وصل بشه. این تستها معمولاً خیلی ارزون هستن و روی هر CI server در چند ثانیه اجرا میشن، برای همین باید حجم بیشترشون رو همین سطح تشکیل بده.
integration test
integration test مطمئن میشه که چند تا ماژول یا سرویس که با هم کار میکنن، درست با هم هماهنگ هستن — مثلاً تعامل با دیتابیس، یا اینکه دو تا میکروسرویس واقعاً با هم درست حرف میزنن. اینها گرونتر از یونیت تست هستن، چون نیاز دارن چند بخش از سیستم واقعاً بالا و در حال اجرا باشن.
functional test
functional test روی نیازمندیهای کسبوکار تمرکز داره؛ فقط خروجی نهایی یه عملیات رو چک میکنه، نه حالتهای میانی سیستم. یه سردرگمی رایج بین integration test و functional test هست، چون هر دو نیاز به تعامل چند مؤلفه دارن. فرقشون اینه: یه integration test شاید فقط چک کنه که میتونی از دیتابیس کوئری بگیری، در حالی که یه functional test انتظار داره دقیقاً همون مقداری که طبق نیازمندی محصول تعریف شده، از دیتابیس برگرده.
end-to-end test
end-to-end یا E2E test رفتار واقعی کاربر رو توی یه محیط کامل شبیهسازی میکنه — از باز کردن یه صفحهی ساده تا سناریوهای پیچیدهتر مثل پرداخت آنلاین یا دریافت ایمیل تأیید. این تستها خیلی مفیدن، ولی هم گرون هستن و هم نگهداریشون سخته؛ برای همین توصیهی رایج اینه که فقط چند تا سناریوی کلیدی رو با E2E پوشش بدی و بیشتر تکیهت روی یونیت و integration تست باشه که سریعتر مشکل رو نشونت میدن.
acceptance test
acceptance test یه تست رسمیتره که چک میکنه آیا کل سیستم نیازمندیهای کسبوکار رو برآورده میکنه یا نه؛ نیاز داره کل اپلیکیشن در حال اجرا باشه و روی رفتار واقعی کاربر تمرکز داره. بعضی وقتها فراتر هم میره و performance سیستم رو هم اندازه میگیره، و اگه به یه هدف مشخص نرسه، تغییرات رو رد میکنه.
performance test
performance test میسنجه سیستم زیر یه بار مشخص چطور رفتار میکنه — قابلیت اطمینان، سرعت، مقیاسپذیری و responsiveness. مثلاً میتونه زمان پاسخدهی رو زیر تعداد زیادی request بسنجه، یا رفتار سیستم رو با حجم زیادی داده تست کنه. اینجور تستها کمک میکنن گلوگاهها (bottleneck) رو پیدا کنی و پایداری سیستم رو زیر ترافیک اوج بسنجی.
smoke test
smoke test یه تست پایه و سریعه که فقط چک میکنه عملکردهای اصلی سیستم کار میکنن یا نه. هدفش این نیست که همهچیز رو دقیق بررسی کنه، فقط میخواد یه اطمینان اولیه بده. جای خوب استفادهازش درست بعد از یه build جدید (برای تصمیمگیری که آیا ارزش داره تستهای گرونتر رو اجرا کنی یا نه) یا بلافاصله بعد از یه deploy هست، تا مطمئن بشی محیط جدید درست بالا اومده.
اتوماسیون این تستها چطور انجام میشه؟
برای خودکارسازی این تستها، اول باید اونها رو با یه framework مناسب زبانت بنویسی — چیزهایی مثل PHPUnit برای PHP، Mocha برای جاوااسکریپت، یا RSpec برای روبی. بعد از اینکه تستهات از طریق ترمینال و با یه دستور ساده قابل اجرا شدن، میتونی بذاری یه CI server یا سرویس ابری مثل Bitbucket Pipelines هر بار که یه تغییر جدید push میشه، خودکار اجراشون کنه.
exploratory testing، جایی که تست دستی هنوز جواب میده
با اینکه اتوماسیون کلید اصلی مقیاسپذیری تسته، این به این معنی نیست که تست دستی کاملاً بیفایده شده. اینجاست که exploratory testing وارد میشه — یه رویکرد دستی و بدون اسکریپت از پیش نوشتهشده که هدفش پیدا کردن باگهای غیربدیهیه، چیزهایی که هیچ تست خودکاری از قبل براش نوشته نشده چون کسی اصلاً فکرش رو نکرده بود. یه توصیهی عملی برای این نوع تست اینه که هر نشست exploratory بیشتر از دو ساعت طول نکشه و یه محدودهی مشخص داشته باشه، تا تسترها روی یه بخش خاص از سیستم تمرکز کنن.
یه نکتهی مهم دربارهی هدف تست
یه چیزی که خیلی وقتها فراموش میشه اینه که تست فقط برای چک کردن «آیا کاربر میتونه کارشو انجام بده» نیست؛ به همون اندازه مهمه که چک کنی اپلیکیشن با دادهی بد یا رفتار غیرمنتظره خراب نمیشه. باید پیشبینی کنی وقتی کاربر یه تایپو میزنه، یه فرم ناقص رو submit میکنه، یا از یه API به شکل اشتباه استفاده میکنه چه اتفاقی میفته، و اینکه آیا کسی میتونه راحت دادهای رو دستکاری کنه یا به منبعی که نباید بهش دسترسی داشته باشه برسه. یه test suite خوب باید تلاش کنه اپ رو بشکنه، نه فقط ثابت کنه کار میکنه.
یه نکتهی آخر هم هست که ارزش تکرار داره: تستها هم خودشون کد هستن. یعنی موقع code review نباید فراموششون کنی — گاهی همین تستها آخرین دروازهای هستن که جلوی یه باگ رو قبل از رسیدن به production میگیرن.
این تقسیمبندی که اینجا مرور کردیم، بر پایهی یه راهنمای خوب از Atlassian دربارهٔ انواع تست نرمافزاره؛ اگه دوست داری جزئیات بیشتری از دید خود Atlassian ببینی، میتونی این مقاله رو هم بخونی.
