آموزش تست Mutation در سالیدیتی

تست Mutation در سالیدیتی یکی از روش‌های بررسی کیفیت تست های واحد (test suite) است. در این روش، به‌صورت عمدی خطاهایی در کد ایجاد می‌شود تا بررسی شود که آیا تست ها می‌توانند این خطاها را شناسایی و گزارش کنند یا خیر.

نوع خطاهایی که به‌صورت مصنوعی وارد کد می‌شوند معمولاً ساده و قابل پیش‌بینی هستند. به مثال زیر توجه کنید:

در این مثال، عملگر مقایسه از “>=” به “<” تغییر کرده است. اگر تست‌های واحد همچنان بدون خطا اجرا شوند، این نشان می‌دهد که تست ها نتوانسته‌اند تفاوت معنادار در منطق کد را تشخیص دهند. در نتیجه، اطمینانی که از این تست ها حاصل می‌شود نادرست است.

نکته بسیار مهم این است که خطاهای ایجادشده باید از نظر نحوی (syntax) معتبر باشند؛ یعنی کد حاصل همچنان باید قابل کامپایل شدن در سالیدیتی باشد. اگر کد جدید کامپایل نشود، اجرای تست های واحد نیز ممکن نخواهد بود.

پوشش خط (Line Coverage) بدون تست واقعی

فرض کنیم از مثال پیش‌فرضی که Foundry پس از اجرای دستور forge init ایجاد می‌کند استفاده کنیم، و سپس همه خطوط assert را غیرفعال کنیم:

اگر در این حالت دستور forge coverage را اجرا کنیم، نتیجه به‌صورت جدولی نمایش داده می‌شود که ادعا می‌کند فایل Counter.sol دارای ۱۰۰٪ پوشش خط و شاخه (line and branch coverage) است، در حالی که هیچ خطی از کد واقعاً تست نشده است!

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

البته این مثال، نمونه‌ای واضح از اشتباه در نگارش تست ها است. اما در عمل، ممکن است هنگام تلاش برای افزایش عدد پوشش کد (coverage score) ناخواسته دچار همین خطا شویم. باید توجه داشت که “پوشش” تنها نشان می‌دهد که یک مسیر اجرایی در کد اجرا شده و ریورتی رخ نداده است. این به‌تنهایی نشان‌دهنده صحت رفتار برنامه نیست.

آنچه واقعاً اهمیت دارد، این است که مطمئن شویم تغییرات مورد انتظار در وضعیت قرارداد واقعاً اتفاق افتاده‌اند.

انواع جهش ها (Kinds of Mutants)

در تست جهش (Mutation Testing)، می‌توان انواع مختلفی از تغییرات (جهش ها) را برای بررسی اثربخشی تست ها اعمال کرد. در ادامه، برخی از رایج‌ترین و مفیدترین انواع جهش ها آورده شده‌اند:

  • حذف مادیفایرهای تابع (function modifiers)

  • معکوس کردن مقایسه‌های نابرابری

  • تغییر مقادیر ثابت یا جایگزینی رشته ها با رشته‌های خالی

  • جایگزینی true با false

  • تغییر عملگرهای منطقی و بیتی

  • تغییر عملگرهای حسابی

  • حذف خطوط کد

  • جابجایی خطوط کد

تست Mutation خودکار

اعمال دستی تغییرات در کد بر اساس قواعد جهش‌سازی بسیار زمان‌بر و خسته‌کننده است. برای حل این مشکل، ابزارهایی طراحی شده‌اند که این فرآیند را به‌صورت خودکار انجام می‌دهند. این ابزارها به‌طور سیستماتیک چندین تغییر احتمالی (mutation) ایجاد می‌کنند، کد را تغییر می‌دهند، تست‌ها را اجرا می‌کنند، نتایج را ذخیره می‌کنند و در نهایت گزارشی جامع ارائه می‌دهند.

ابزارهای تست جهش معمولاً یکی از سه نتیجه زیر را ثبت می‌کنند:

  1. جهش‌ یافته باقی مانده (mutant survived)

  2. جهش‌ یافته معادل (equivalent mutant)

  3. جهش‌ یافته حذف شده (mutant killed)

جهش باقی مانده (Mutant Survived) به این معناست که کد دچار تغییر شده اما تست همچنان بدون خطا عبور کرده است. این وضعیت نشان می‌دهد که تست ها نتوانسته‌اند تغییر اعمال‌شده را تشخیص دهند، بنابراین نمی‌توان به آن‌ها برای تضمین درستی رفتار برنامه اعتماد کرد.

مثالی از حالتی که جهش معادل ممکن است رخ دهد:

در برخی شرایط، کامپایلر ممکن است پس از اعمال جهش‌هایی مانند تغییر ترتیب خطوط، دقیقاً همان بایت‌کد قبلی را تولید کند. چنین حالتی را جهش معادل (Equivalent Mutation) می‌نامند. وجود جهش های معادل می‌تواند نشان‌دهنده بخش‌هایی از کد باشد که غیرفعال یا بی‌اثر (dead code) هستند.

در مقابل، سناریوی مطلوب، “جهش حذف شده (Mutant Killed)” است. این نتیجه نشان می‌دهد که کد تغییر کرده و تست ها با شکست مواجه شده‌اند. یعنی تست ها توانسته‌اند رفتار نادرست یا خطای ایجادشده را تشخیص دهند، که نشانه عملکرد صحیح تست‌هاست.

اگر نتیجه جهش باعث شود کد دیگر کامپایل نشود، مثلاً یک متغیر مهم حذف شده باشد ولی در بخش دیگری از کد مورد استفاده قرار بگیرد، ابزار تست این حالت را نیز به‌عنوان “جهش حذف شده” ثبت می‌کند. در واقع، ناتوانی کد در کامپایل به‌معنای شکست تست هاست و نشان می‌دهد که سیستم تست در شناسایی خطا موفق بوده است.

پوشش 100% خطوط و شاخه ها (Line and Branch Coverage) برای تست Mutation ضروری است

برای اینکه تست جهش مؤثر باشد، لازم است تمام خطوط و شاخه‌های منطقی کد توسط تست ها پوشش داده شوند. اگر خط یا شاخه‌ای از کد در تست‌ها اجرا نشود، اعمال تغییر (جهش) روی آن قسمت هیچ تأثیری در نتیجه تست نخواهد داشت و تست ها همچنان بدون خطا عبور می‌کنند.

به مثال زیر توجه کنید:

در این تابع، مادیفایر onlyMinter در واقع یک شاخه منطقی (branch) ضمنی ایجاد می‌کند. اگر تست ها فقط حالتی را بررسی کنند که در آن کاربر مجاز (minter) تابع را فراخوانی می‌کند، حذف کردن onlyMinter باعث شکست تست نخواهد شد. چون تست ها اصلاً وضعیت ورود کاربران غیرمجاز را بررسی نکرده‌اند.

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

جالب است بدانید که این مثال، با وجود ساده و ساختگی به‌نظر رسیدن، واقعاً از یکی از گزارش های رسمی CodeArena استخراج شده است. این موضوع نشان می‌دهد که حتی پروژه‌های واقعی نیز ممکن است بدون پوشش کامل، در معرض خطاهای پنهان قرار بگیرند.

خطاهای Off-by-One و شرایط مرزی (Boundary Conditions)

تست های جهش (Mutation tests) در شناسایی خطاهای رایج از نوع “یک واحد اختلاف” (off-by-one errors) نقش بسیار مؤثری دارند. این نوع خطا زمانی رخ می‌دهد که یک محدودیت، مقدار دقیق مرزی را به‌درستی در نظر نمی‌گیرد.

به مثال زیر توجه کنید:

در اینجا، اپراتور مقایسه از < به <= تغییر کرده است؛ یعنی حالا مقدار amount می‌تواند برابر با LIMIT باشد، در حالی که در منطق اصلی چنین چیزی مجاز نبود.

فرض کنیم در تست های واحد، مقدار amount را فقط برای اعداد ۳ و ۸ بررسی کرده باشیم. در این حالت، با اینکه تست ها شاخه های مختلف تابع را پوشش می‌دهند و گزارش “پوشش صددرصدی شاخه” صادر می‌شود، اما تست جهش به‌درستی شکست می‌خورد. چرا؟

چون مقدار مرزی (۵ یا حتی ۴) در تست ها بررسی نشده و تغییر منطق شرط توسط تست ها شناسایی نشده است. این یعنی تست ها منطق دقیق مورد نظر برنامه نویس را به‌درستی بیان نکرده‌اند.

در واقع، فقط با بررسی مقدارهایی مانند ۳ و ۸، نمی‌توان به‌صورت کامل رفتار تابع را مشخص کرد. برای تعریف دقیق و کامل مشخصات عملکرد تابع، تست ها باید مقدارهایی را نیز در محدوده مرزی مثل ۴ و ۵ پوشش دهند.

ابزارهای تست Mutation در سالیدیتی

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

برخی از ابزارهای شناخته‌شده در این حوزه عبارتند از:

این ابزارها توانایی تولید جهش های متنوع را دارند، اما در بیشتر موارد، اجرای تست ها و تحلیل نتایج به‌صورت دستی انجام می‌شود. به همین دلیل، استفاده مؤثر از آن‌ها نیازمند آشنایی با نحوه اجرای دقیق تست ها و بررسی رفتارهای غیرمنتظره در کد است.

توسعه دهندگان هنگام انتخاب ابزار، باید به فاکتورهایی مانند پشتیبانی از فریم‌ورک‌هایی نظیر Foundry، Hardhat یا Truffle، سازگاری با نسخه‌های جدید سالیدیتی، و میزان پشتیبانی و به‌روزرسانی ابزار توجه داشته باشند. همچنین برخی از ابزارهای قدیمی ممکن است دیگر نگهداری نشوند و برای پروژه‌های جدید مناسب نباشند.

امتیاز جهش (Mutation Score)

در برخی زبان های برنامه نویسی، ابزارهای تست جهش یک امتیاز جهش نیز ارائه می‌دهند. این امتیاز نشان‌دهنده درصد جهش هایی است که توسط تست های واحد شناسایی و “حذف” شده‌اند. اگر ۱۰۰٪ جهش ها شناسایی شوند، می‌توان با اطمینان بالا به تست‌ها اعتماد کرد که در صورت بروز تغییرات ناخواسته یا تصادفی در کد، واکنش مناسبی نشان خواهند داد.

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

به همین دلیل، در حوزه قراردادهای سالیدیتی، دستیابی به امتیاز جهش ۱۰۰٪ هدفی منطقی و ارزشمند است. حتی اگر فقط تعداد کمی جهش باقی مانده باشند، باید با دقت کامل مورد بررسی قرار بگیرند، چرا که ممکن است نشان‌دهنده ضعف جدی در پوشش تست‌ها باشند.

محدودیت‌های تست Mutation

اگرچه تست جهش (mutation test) ابزار قدرتمندی برای سنجش کیفیت تست‌های واحد محسوب می‌شود، اما این روش نیز محدودیت‌هایی دارد:

تست های واحد در اکثر موارد بدون وضعیت (stateless) هستند. بسیاری از منطق‌های تجاری در قراردادهای هوشمند بر اساس وضعیت متغیر سیستم عمل می‌کنند. به همین دلیل، تست جهش به‌تنهایی نمی‌تواند بررسی کند که این منطق ها به‌درستی توسط تست ها پوشش داده شده‌اند.

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

5/5 - (1 امتیاز)

راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.

آموزش انیمیشن‌ سازی دو بعدی با موهو – خلق انیمیشن‌ های خلاقانه شبیه دیرین دیرین
  • انتشار: ۶ مرداد ۱۴۰۴

دسته بندی موضوعات

آخرین محصولات فروشگاه

مشاهده همه

نظرات

بازخوردهای خود را برای ما ارسال کنید