آزمایش واحد (یونیت) فرایندی است که در آن کوچکترین واحد عملکردی کد، آزمایش میشود. این آزمایش به تضمین کیفیت کد کمک میکند و بخشی ضروری از توسعه نرمافزار به شمار میآید و یکی از بهترین روشها در توسعه نرمافزار است که آن را به یونیتهای کوچک و عملکردی تبدیل می کند . میتوان ابتدا آزمایشهای یونیت را در قالب کد نوشت و سپس هر تغییراتی که در کد نرمافزار ایجاد میشود، آزمایش خودکار اجرا شود. به این ترتیب، اگر آزمایشی موفقیتآمیز نباشد، میتوان سریعاً قسمت اشتباه کد را شناسایی کرد. آزمایش، تفکر مدولار را تقویت کرده و کیفیت آزمایش را بهتر می کند. این آزمایش خودکار، زمان بیشتری برای تمرکز بر کدنویسی به وجود می آورد.
آزمایش واحد چیست؟
تست واحد (Unit Test) یک بلوک کد است که صحت عملکرد یک بلوک کوچک و ایزوله از کد برنامه، معمولاً یک تابع یا متد، را تأیید میکند. تست واحد برای بررسی این طراحی شده است که بلوک کد طبق منطق نظری توسعهدهنده به درستی اجرا شود. تست واحد تنها قادر است از طریق ورودیها و خروجیهای تأییدشده (صحیح یا نادرست) با بلوک کد تعامل داشته باشد.
یک بلوک کد ممکن است مجموعهای از تستهای واحد داشته باشد که به عنوان موارد آزمایشی (Test Cases) شناخته میشوند. مجموعه کامل موارد آزمایشی تمام رفتارهای مورد انتظار بلوک کد را پوشش میدهد، اما همیشه لازم نیست که مجموعه کامل موارد آزمایشی تعریف شود.
هنگامی که یک بلوک کد برای اجرا به سایر بخشهای سیستم نیاز دارد، نمیتوان از تست واحد با دادههای خارجی استفاده کرد. تست واحد باید به صورت ایزوله اجرا شود. سایر دادههای سیستم، مانند پایگاههای داده، اشیاء یا ارتباطات شبکهای، ممکن است برای عملکرد کد مورد نیاز باشند. در این صورت، باید از دادههای جعلی (Data Stubs) استفاده شود. نوشتن تستهای واحد برای بلوکهای کد کوچک و از نظر منطقی ساده آسانتر است.
استراتژیهای تست واحد
برای ایجاد تستهای واحد، میتوانید از تکنیکهای اساسی زیر برای اطمینان از پوشش تمام موارد آزمایشی استفاده کنید:
- بررسیهای منطقی: آیا سیستم محاسبات درست را انجام میدهد و مسیر درستی را در کد با توجه به ورودیهای صحیح و مورد انتظار طی میکند؟ آیا تمام مسیرهای کد با ورودیهای دادهشده پوشش داده شدهاند؟
- بررسیهای مرزی: برای ورودیهای دادهشده، سیستم چگونه پاسخ میدهد؟ چگونه به ورودیهای معمولی، موارد مرزی یا ورودیهای نامعتبر واکنش نشان میدهد؟ برای مثال، فرض کنید انتظار یک ورودی عدد صحیح بین ۳ تا ۷ دارید. سیستم به عدد ۵ (ورودی معمولی)، عدد ۳ (مورد مرزی) یا عدد ۹ (ورودی نامعتبر) چگونه پاسخ میدهد؟
- مدیریت خطا: وقتی خطاهایی در ورودیها وجود دارد، سیستم چگونه پاسخ میدهد؟ آیا از کاربر برای ورودی دیگری درخواست میشود؟ آیا نرمافزار خراب میشود؟
- بررسیهای شیءگرا: اگر وضعیت اشیاء پایدار با اجرای کد تغییر کند، آیا شیء به درستی بهروزرسانی میشود؟
مثال تست واحد
در اینجا یک مثال از یک متد بسیار ساده در پایتون و چند مورد آزمایشی با کد تست واحد مربوطه آورده شده است:
متد پایتون
def add_two_numbers(x, y):
return x + y
تستهای واحد مربوطه
def test_add_positives():
result = add_two_numbers(5, 40)
assert result == 45
def test_add_negatives():
result = add_two_numbers(-4, -50)
assert result == -54
def test_add_mixed():
result = add_two_numbers(5, -5)
assert result == 0
مزایای تست واحد
تست واحد مزایای زیادی برای پروژههای توسعه نرمافزار دارد:
- کشف کارآمد اشکالات: اگر خطاهایی مبتنی بر ورودی، خروجی یا منطق در یک بلوک کد وجود داشته باشد، تستهای واحد به شما کمک میکنند تا این اشکالات را قبل از رسیدن به مرحله تولید شناسایی کنید. وقتی کد تغییر میکند، همان مجموعه تستهای واحد – همراه با سایر تستها مانند تستهای یکپارچهسازی – اجرا میشوند و انتظار میرود همان نتایج را بدهند. اگر تستها شکست بخورند (که به آن تستهای شکسته نیز گفته میشود)، نشاندهنده اشکالات مبتنی بر رگرسیون است. تست واحد همچنین به یافتن سریعتر اشکالات در کد کمک میکند. توسعهدهندگان شما زمان زیادی را صرف فعالیتهای رفع اشکال نمیکنند. آنها میتوانند به سرعت بخش دقیق کد که دارای خطا است را شناسایی کنند.
- مستندسازی: مستندسازی کد برای دانستن دقیق آنچه کد قرار است انجام دهد مهم است. با این حال، تستهای واحد نیز به عنوان شکلی از مستندسازی عمل میکنند. توسعهدهندگان دیگر تستها را میخوانند تا ببینند کد در هنگام اجرا چه رفتارهایی باید نشان دهد. آنها از این اطلاعات برای اصلاح یا بازسازی (Refactoring) کد استفاده میکنند. بازسازی کد باعث بهبود عملکرد و ساختار بهتر آن میشود. میتوانید تست واحد را دوباره اجرا کنید تا بررسی کنید که کد پس از تغییرات به درستی کار میکند.
توسعهدهندگان چگونه از تستهای واحد استفاده میکنند؟
توسعهدهندگان در مراحل مختلف چرخه عمر توسعه نرمافزار از تستهای واحد استفاده میکنند:
- توسعه مبتنی بر تست (TDD): توسعه مبتنی بر تست زمانی است که توسعهدهندگان تستهایی را برای بررسی الزامات عملکردی یک قطعه نرمافزار قبل از ساخت کامل کد میسازند. با نوشتن تستها در ابتدا، کد بلافاصله پس از تکمیل کدنویسی و اجرای تستها قابل تأیید در برابر الزامات است.
- پس از تکمیل یک بلوک کد: هنگامی که یک بلوک کد کامل در نظر گرفته میشود، اگر هنوز به دلیل TDD ایجاد نشده باشند، باید تستهای واحد توسعه یابند. سپس میتوانید بلافاصله تستهای واحد را برای تأیید نتایج اجرا کنید. تستهای واحد همچنین به عنوان بخشی از مجموعه کامل سایر تستهای نرمافزاری در طول تست سیستم اجرا میشوند. آنها معمولاً اولین مجموعه تستهایی هستند که در طول تست کامل نرمافزار سیستم اجرا میشوند.
- کارایی DevOps: یکی از فعالیتهای اصلی در کاربرد DevOps در شیوههای توسعه نرمافزار، یکپارچهسازی مداوم و تحویل مداوم (CI/CD) است. هر گونه تغییر در کد به طور خودکار در پایگاه کد گستردهتر ادغام میشود، از طریق تست خودکار اجرا میشود و سپس در صورت قبولی در تستها مستقر میشود. تستهای واحد بخشی از مجموعه تستها را همراه با تست یکپارچهسازی تشکیل میدهند. آنها به طور خودکار در خط لوله CI/CD اجرا میشوند تا کیفیت کد را در حین ارتقا و تغییر در طول زمان تضمین کنند.
چه زمانی تست واحد کمتر مفید است؟
تست واحد همیشه برای هر مورد آزمایشی در هر بلوک کد در هر پروژه لازم نیست. در اینجا چند نمونه از مواردی که ممکن است تست واحد کنار گذاشته شود آورده شده است:
- وقتی زمان محدود است: حتی با چارچوبهای تست واحد تولیدی، نوشتن تستهای واحد جدید زمان قابلتوجهی از توسعهدهندگان شما میگیرد. در حالی که تستهای واحد مبتنی بر ورودی و خروجی ممکن است به راحتی تولید شوند، بررسیهای مبتنی بر منطق دشوارتر هستند. هنگامی که توسعهدهندگان شروع به نوشتن تستها میکنند، ممکن است فرصتهای بازسازی در بلوک کد را ببینند و از تکمیل آنها منحرف شوند. این میتواند منجر به طولانی شدن زمانبندی توسعه و مشکلات بودجهای شود.
- برنامههای UI/UX: وقتی سیستم اصلی به ظاهر و احساس مربوط است تا منطق، ممکن است تستهای واحد زیادی برای اجرا وجود نداشته باشد. سایر انواع تست، مانند تست دستی، در این موارد استراتژی بهتری نسبت به تست واحد هستند.
- پایگاههای کد قدیمی: نوشتن تستهایی برای پوشش کد قدیمی موجود میتواند بسته به سبک کد نوشتهشده تقریباً غیرممکن باشد. از آنجا که تستهای واحد به دادههای جعلی نیاز دارند، نوشتن تستهای واحد برای سیستمهای بسیار بههمپیوسته با تجزیه دادههای زیاد میتواند بیش از حد زمانبر باشد.
- الزامات بهسرعت در حال تغییر: بسته به پروژه، نرمافزار میتواند رشد کند، جهت تغییر کند، یا بخشهای کاملی در هر دوره کاری کنار گذاشته شود. اگر احتمال تغییر مکرر الزامات وجود داشته باشد، دلیلی برای نوشتن تستهای واحد برای هر بلوک کد توسعهیافته وجود ندارد.
بهترین روشهای تست واحد
ما چند بهترین روش تست واحد را برای بهرهبرداری حداکثری از فرآیند شما ارائه میدهیم:
- استفاده از چارچوب تست واحد: نوشتن تستهای واحد صریح و کاملاً سفارشی برای هر بلوک کد اتلاف وقت است. چارچوبهای تست خودکار برای هر زبان برنامهنویسی محبوب وجود دارد. به عنوان مثال، پایتون دارای pytest و unittest به عنوان دو چارچوب مختلف برای تست واحد است. چارچوبهای تست به طور گسترده در پروژههای توسعه نرمافزار در تمام اندازهها استفاده میشوند.
- اتوماسیون تست واحد: تست واحد باید در رویدادهای مختلف در توسعه نرمافزار فعال شود. به عنوان مثال، میتوانید از آنها قبل از ارسال تغییرات به یک شاخه با استفاده از نرمافزار کنترل نسخه یا قبل از استقرار بهروزرسانی نرمافزار استفاده کنید. تست واحد همچنین ممکن است روی یک پروژه کامل، بر اساس یک برنامه زمانی تنظیمشده اجرا شود. تست واحد خودکار تضمین میکند که تستها در تمام رویدادها و موارد مناسب در طول چرخه عمر توسعه اجرا شوند.
- تأیید یکبار: برای هر تست واحد، باید تنها یک نتیجه صحیح یا نادرست وجود داشته باشد. اطمینان حاصل کنید که تنها یک دستور تأیید (assert) در تست شما وجود دارد. یک دستور تأیید ناموفق در بلوکی با چندین دستور میتواند باعث سردرگمی در مورد اینکه کدام یک مشکل را ایجاد کرده است، شود.
- اجرای تست واحد: تست واحد بخش مهمی از ساخت نرمافزار است، اما بسیاری از پروژهها منابع را به آن اختصاص نمیدهند. زمانی که پروژهها به عنوان نمونه اولیه شروع میشوند، تلاشهای کوچک مبتنی بر جامعه هستند، یا به سرعت کدنویسی میشوند، تست واحد ممکن است به دلیل محدودیتهای زمانی کنار گذاشته شود. با این حال، هنگامی که پروژهها را با تست واحد به عنوان یک روش استاندارد از ابتدا میسازید، فرآیند بسیار آسانتر برای دنبال کردن و تکرار میشود.
تفاوت بین تست واحد و سایر انواع تست چیست؟
انواع مختلفی از روشهای تست نرمافزار در کنار تست واحد وجود دارد. همه آنها نقشهای خاصی در چرخه عمر توسعه نرمافزار ایفا میکنند:
- تست یکپارچهسازی: بررسی میکند که بخشهای مختلف سیستم نرمافزاری که برای تعامل طراحی شدهاند به درستی این کار را انجام میدهند.
- تست عملکردی: بررسی میکند که آیا سیستم نرمافزاری الزامات نرمافزاری مشخصشده قبل از ساخت را برآورده میکند.
- تست عملکرد: بررسی میکند که آیا نرمافزار به الزامات عملکرد مورد انتظار، مانند سرعت و اندازه حافظه، عمل میکند.
- تست پذیرش: زمانی است که نرمافزار به صورت دستی توسط ذینفعان یا گروههای کاربری آزمایش میشود تا بررسی شود که آیا به گونهای که انتظار دارند کار میکند.
- تست امنیتی: نرمافزار را در برابر آسیبپذیریها و تهدیدات شناختهشده بررسی میکند. این شامل تحلیل سطح تهدید، از جمله نقاط ورودی شخص ثالث به نرمافزار است.
این روشهای تست معمولاً به ابزارهای تخصصی و فرآیندهای مستقل برای بررسی نرمافزار نیاز دارند. بسیاری از آنها همچنین پس از توسعه عملکرد اولیه برنامه انجام میشوند. در مقابل، تستهای واحد هر بار که کد ساخته میشود اجرا میشوند. آنها میتوانند به محض نوشتن هر کدی نوشته شوند و برای اجرا به ابزار خاصی نیاز ندارند. تست واحد به عنوان یکی از اساسیترین انواع تست نرمافزار در نظر گرفته میشود.

