تست یونیت (Unit Test) چیست و چه اهمیتی دارد؟
تست یونیت (Unit Testing) به فرایند نوشتن و اجرای خودکار تستها جهت اطمینان یافتن از اجرای مطابق انتظار کدنویسی تابعها گفته میشود. با این که شاید فکر کنید تست یونیت دوبارهکاری محسوب میشود، اما در واقع این یک اقدام پیشگیرانه برای جلوگیری از بروز باگ پیش از وقوع آن است.
تست یونیت چیست؟
پیش از آن که در مورد تست یونیت توضیح دهیم، باید بدانیم که منظور از خود یونیت چیست. «یونیت» (Unit) به کوچکترین مؤلفه نرمافزاری ممکن در اپلیکیشن (مانند تابع، کلاس یا کامپوننت) گفته میشود. تستهای یونیت منفرد ما را مطمئن میسازند که کامپوننت مرکزی اپلیکیشن، مطابق انتظار عمل میکند و این که یک کامیت فیچر به یک بخش از اپلیکیشن موجب بروز مشکلی در بخش دیگر نمیشود. اگر چنین مسئله رخ دهد احتمالاً در یکی از کدهای قدیمی یا جدید باگ دارید یا یک تست ضعیف یا قدیمی نوشتهاید.
هدف تست یونیت کاملاً روشن است و چیزی جز کاهش باگ نیست. تست یونیت به طور خاص باگها را هدف میگیرد که از یکپارچهسازی (integration) نشات میگیرند. توسعهدهنده ممکن است فکر کند که همه چیز به صورت لوکال درست است و کدش را کامیت کند تا بفهمد آیا این کامیت موجب از کار افتادن اپلیکیشن میشود یا نه. تست یونیت موجب میشود بتوانیم بسیاری از این نقصها را پیش از آن که واقع شوند، به دام بیندازیم و زمانی که آن را با روشهای یکپارچهسازی پیوسته خودکارشده ادغام کنیم، میتوانیم مطمئن باشیم که همه چیز به درستی کار خواهد کرد.
البته تست یونیت همواره به قطعات کوچک کد محدود نمیشود، بلکه میتوان روی کامپوننتهای بزرگ نیز که از چند تابع دیگر (که خود تست یونیت شدهاند) استفاده میکنند، از تست یونیت استفاده کرد. این کار به ردگیری خطاها به روش مؤثرتر کمک میکند و تشخیص میدهد که آیا خطا در متدهای شیء کامپوننت بزرگتر است یا در کامپوننت دیگری است که از آن استفاده میکند.
با این که تست یونیت حائز اهمیت بالایی است، اما تنها نوع تستی نیست که میتوان اجرا کرد. اجرای تست سربهسر UI و مرور دستی کد میتواند بسیاری از باگهای منطقی که تست یونیت ممکن است از دست بدهد را به دام بیندازد.
اجرای تست یونیت منجر به کدبیس تمیزتر میشود
یکی از مشکلات اصلی کدبیسهای قدیمی «کد دایناسور» نام دارد. منظور از کد دایناسور، کدی چنان قدیمی است که اساساً تبدیل به یک جعبه سیاه شده است و ممکن است هیچ کس در مورد طرز کار آن ایدهای نداشته باشد، اما به هر حال کار میکند و شما دوست ندارید آن را ریفکتور کنید، زیرا این ترس را دارید که ممکن است همه چیز را از کار بیندازد.
به این ترتیب زمانی که تست یونیت را مینویسید، در واقع مشغول نوشتن مستندات برای کد هستید. شاید لازم نباشد یک راهنمای کامل بنویسید، اما همواره دست کم دو چیز را تعریف میکنید: چیزی که باید به تابع داده شود و چیزی که بازگشت میدهد. این فرایند تا حدود زیادی شبیه به روش تعریف اسکیمای API است. با استفاده از همین دو موضوع، کاری که تابع انجام میدهد روشن میشود و روش یکپارچهسازی آن در اپلیکیشن نیز مشخص خواهد شد. بدیهی است که تست یونیت مشکل کدبیسهای قدمی موجود را حل نمیکند، بلکه در وهله نخست از نوشتن این نوع کدهای دایناسور جلوگیری میکند.
در اغلب موارد میتوان تست را پیش از نوشتن خود تابعی که قرار است تست شود، نوشت. اگر میدانید که تابع شما به چه چیزی نیاز دارد، نوشتن قبلی تست میتواند شما را در مورد روش به دست آوردن نتیجه نهایی از کد و کارهایی که مسئولیت اجرای آن را دارد کمک کند.
اگر این تأثیر تست یونیت را دوست دارید، ممکن است به TypeScript علاقهمند باشید. تایپ اسکریپت یک سوپرست کامپایل شونده از جاوا اسکریپت است که نوعبندی قوی دارد. ممکن است در زمان استفاده از تایپ اسکریپت نیز بخواهید تستهای یونیت بنویسید، اما با دانستن نوع دادهای که به یک تابع میدهید و از آن میگیرید، ابزار بسیار قدرتمندی در زمان کدنویسی در اختیار خواهید داشت.
شیوه اجرای تست یونیت
فریمورکهای مختلفی برای اجرای تست یونیت وجود دارند و این که بخواهید از کدام یک از آنها استفاده کنید، به زبانی که در آن تست میکنید وابسته خواهد بود. با این حال برای این که طرز کار را به شما نشان دهیم در ادامه از فریمورک Jest استفاده میکنیم که یک فریمورک برای تست کدهای جاوا اسکریپت است و به صورت پیشفرض برای اپلیکیشنهای React مورد استفاده قرار میگیرد.
هر تست یونیت به طور معمول شامل سه مرحله است:
- چیدمان (Arrange) – در این مرحله دادهها برای تست یونیت آماده میشوند. اگر لازم است که دادهها واکشی شوند، باید یک شیء پیچیده بسازید یا صرفاً مواردی که برای این مرحله لازم هستند آماده کنید.
- عمل (Act) – در این مرحله یونیت فراخوانی میشود و پاسخ نیز لاگ خواهد شد.
- تأکید (Assert) – در این مرحله عمده اتفاقات تست رخ میدهد. این همان جایی است که عملگرهای بولی بر مبنای آن نوشته میشوند.
اگر هر کدام از تأکیدها ناموفق باشند، یونیت نمیتواند تست را پاس کند و یک لاگ تفصیلی و «رد پشته» (stack trace) در مورد چیزی که اشتباه بوده است، چیزی که انتظار میرفته است و چیزی که در عمل رخ داده است، دریافت میکنید.
Jest چندین Matcher مختلف دارد که به ما امکان میدهند تا تأکیدهای سریع و سادهای را اجرا کنیم. برای نمونه فرض کنید تابع زیر را داریم که دو عدد را با هم جمع میزند:
1function doSomeMath(a, b) {
2 return a + b;
3}
میتوانید این تابع را با استفاده از گزاره زیر تست کنید:
1test('Expect math to work', () => {
2 expect(doSomeMath(1, 1)).toBe(2);
3});
به طور معمول، این تست همراه با تابع زیر مسیر functionName.test.js ذخیره میشود. Jest در زمان اجرای تستها به صورت خودکار به دنبال این فایلها میگردد.
تابع ()toBe. در واقع یک Matcher است که در این مورد برابر بودن را بررسی میکند. Mather-های زیاد دیگری مانند ()toBeEqual. نیز وجود دارند که برابری شیء و یا در مورد ()toContain. محتوای آرایه را بررسی میکنند. برای کسب اطلاعات بیشتر در این مورد به مستندات این کتابخانه (+) مراجعه کنید.
چرا باید با یونیت تست تست کنیم در حالی که میتونیم هرتابعو خودمون ازمایش کنیم ؟
چون قراره بعدا کدت رو توسعه بدی و میخوای خیالت راحت بشه که در آینده موقع توسعه ساید افکت ایجاد نمیشه و کدهای قبلی که نوشته بودی بازم درست کار میکنه
مزایای زیاد دیگه هم داره که تو جواب کامنت جاش نیست