تست Mutation در سالیدیتی یکی از روشهای بررسی کیفیت تست های واحد (test suite) است. در این روش، بهصورت عمدی خطاهایی در کد ایجاد میشود تا بررسی شود که آیا تست ها میتوانند این خطاها را شناسایی و گزارش کنند یا خیر.
نوع خطاهایی که بهصورت مصنوعی وارد کد میشوند معمولاً ساده و قابل پیشبینی هستند. به مثال زیر توجه کنید:
1 2 3 4 5 6 7 8 9 |
// original function function mint() external payable { require(msg.value >= PRICE, "insufficient msg value"); } // mutated function function mint() external public { require(msg.value < PRICE, "insufficient msg value"); } |
در این مثال، عملگر مقایسه از “>=” به “<” تغییر کرده است. اگر تستهای واحد همچنان بدون خطا اجرا شوند، این نشان میدهد که تست ها نتوانستهاند تفاوت معنادار در منطق کد را تشخیص دهند. در نتیجه، اطمینانی که از این تست ها حاصل میشود نادرست است.
نکته بسیار مهم این است که خطاهای ایجادشده باید از نظر نحوی (syntax) معتبر باشند؛ یعنی کد حاصل همچنان باید قابل کامپایل شدن در سالیدیتی باشد. اگر کد جدید کامپایل نشود، اجرای تست های واحد نیز ممکن نخواهد بود.
پوشش خط (Line Coverage) بدون تست واقعی
فرض کنیم از مثال پیشفرضی که Foundry پس از اجرای دستور forge init
ایجاد میکند استفاده کنیم، و سپس همه خطوط assert
را غیرفعال کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "../src/Counter.sol"; contract CounterTest is Test { Counter public counter; function setUp() public { counter = new Counter(); counter.setNumber(0); } function testIncrement() public { counter.increment(); //assertEq(counter.number(), 1); } function testSetNumber(uint256 x) public { counter.setNumber(x); //assertEq(counter.number(), x); } } |
اگر در این حالت دستور forge coverage
را اجرا کنیم، نتیجه بهصورت جدولی نمایش داده میشود که ادعا میکند فایل Counter.sol
دارای ۱۰۰٪ پوشش خط و شاخه (line and branch coverage) است، در حالی که هیچ خطی از کد واقعاً تست نشده است!
یعنی با وجود اینکه هیچ assert
ای برای بررسی درستی رفتار تابعها وجود ندارد، سیستم گزارش میدهد که کل کد تست شده است. در چنین شرایطی، اگر باگ های جدی وارد کد کنیم، تست ها همچنان موفق گزارش میشوند.
البته این مثال، نمونهای واضح از اشتباه در نگارش تست ها است. اما در عمل، ممکن است هنگام تلاش برای افزایش عدد پوشش کد (coverage score) ناخواسته دچار همین خطا شویم. باید توجه داشت که “پوشش” تنها نشان میدهد که یک مسیر اجرایی در کد اجرا شده و ریورتی رخ نداده است. این بهتنهایی نشاندهنده صحت رفتار برنامه نیست.
آنچه واقعاً اهمیت دارد، این است که مطمئن شویم تغییرات مورد انتظار در وضعیت قرارداد واقعاً اتفاق افتادهاند.
انواع جهش ها (Kinds of Mutants)
در تست جهش (Mutation Testing)، میتوان انواع مختلفی از تغییرات (جهش ها) را برای بررسی اثربخشی تست ها اعمال کرد. در ادامه، برخی از رایجترین و مفیدترین انواع جهش ها آورده شدهاند:
-
حذف مادیفایرهای تابع (function modifiers)
-
معکوس کردن مقایسههای نابرابری
-
تغییر مقادیر ثابت یا جایگزینی رشته ها با رشتههای خالی
-
جایگزینی true با false
-
تغییر عملگرهای منطقی و بیتی
-
تغییر عملگرهای حسابی
-
حذف خطوط کد
-
جابجایی خطوط کد
تست Mutation خودکار
اعمال دستی تغییرات در کد بر اساس قواعد جهشسازی بسیار زمانبر و خستهکننده است. برای حل این مشکل، ابزارهایی طراحی شدهاند که این فرآیند را بهصورت خودکار انجام میدهند. این ابزارها بهطور سیستماتیک چندین تغییر احتمالی (mutation) ایجاد میکنند، کد را تغییر میدهند، تستها را اجرا میکنند، نتایج را ذخیره میکنند و در نهایت گزارشی جامع ارائه میدهند.
ابزارهای تست جهش معمولاً یکی از سه نتیجه زیر را ثبت میکنند:
-
جهش یافته باقی مانده (mutant survived)
-
جهش یافته معادل (equivalent mutant)
-
جهش یافته حذف شده (mutant killed)
جهش باقی مانده (Mutant Survived) به این معناست که کد دچار تغییر شده اما تست همچنان بدون خطا عبور کرده است. این وضعیت نشان میدهد که تست ها نتوانستهاند تغییر اعمالشده را تشخیص دهند، بنابراین نمیتوان به آنها برای تضمین درستی رفتار برنامه اعتماد کرد.
مثالی از حالتی که جهش معادل ممکن است رخ دهد:
1 2 3 4 5 6 7 |
// before x = x + 1; y = y + 1; // after y = y + 1; x = x + 1; |
1 2 |
require(false); // کدهای بعد از این خط هیچگاه اجرا نمیشوند |
در مقابل، سناریوی مطلوب، “جهش حذف شده (Mutant Killed)” است. این نتیجه نشان میدهد که کد تغییر کرده و تست ها با شکست مواجه شدهاند. یعنی تست ها توانستهاند رفتار نادرست یا خطای ایجادشده را تشخیص دهند، که نشانه عملکرد صحیح تستهاست.
اگر نتیجه جهش باعث شود کد دیگر کامپایل نشود، مثلاً یک متغیر مهم حذف شده باشد ولی در بخش دیگری از کد مورد استفاده قرار بگیرد، ابزار تست این حالت را نیز بهعنوان “جهش حذف شده” ثبت میکند. در واقع، ناتوانی کد در کامپایل بهمعنای شکست تست هاست و نشان میدهد که سیستم تست در شناسایی خطا موفق بوده است.
پوشش 100% خطوط و شاخه ها (Line and Branch Coverage) برای تست Mutation ضروری است
برای اینکه تست جهش مؤثر باشد، لازم است تمام خطوط و شاخههای منطقی کد توسط تست ها پوشش داده شوند. اگر خط یا شاخهای از کد در تستها اجرا نشود، اعمال تغییر (جهش) روی آن قسمت هیچ تأثیری در نتیجه تست نخواهد داشت و تست ها همچنان بدون خطا عبور میکنند.
به مثال زیر توجه کنید:
1 2 3 |
function mint(address to_, string memory questId_) public onlyMinter { // منطق اصلی برنامه } |
در این تابع، مادیفایر onlyMinter
در واقع یک شاخه منطقی (branch) ضمنی ایجاد میکند. اگر تست ها فقط حالتی را بررسی کنند که در آن کاربر مجاز (minter) تابع را فراخوانی میکند، حذف کردن onlyMinter
باعث شکست تست نخواهد شد. چون تست ها اصلاً وضعیت ورود کاربران غیرمجاز را بررسی نکردهاند.
بنابراین، اگر مادیفایر onlyMinter
نتواند دسترسی افراد غیرمجاز را مسدود کند، تست های فعلی آن را کشف نخواهند کرد و این خطای امنیتی از دید تست ها پنهان میماند.
جالب است بدانید که این مثال، با وجود ساده و ساختگی بهنظر رسیدن، واقعاً از یکی از گزارش های رسمی CodeArena استخراج شده است. این موضوع نشان میدهد که حتی پروژههای واقعی نیز ممکن است بدون پوشش کامل، در معرض خطاهای پنهان قرار بگیرند.
خطاهای Off-by-One و شرایط مرزی (Boundary Conditions)
تست های جهش (Mutation tests) در شناسایی خطاهای رایج از نوع “یک واحد اختلاف” (off-by-one errors) نقش بسیار مؤثری دارند. این نوع خطا زمانی رخ میدهد که یک محدودیت، مقدار دقیق مرزی را بهدرستی در نظر نمیگیرد.
به مثال زیر توجه کنید:
1 2 3 4 5 6 7 8 9 10 11 |
uint256 public LIMIT = 5; // original function mint(uint256 amount) external { require(amount < LIMIT, "exceeds limit"); } // mutation function mint(uint256 amount) external { require(amount <= LIMIT, "exceeds limit"); } |
در اینجا، اپراتور مقایسه از <
به <=
تغییر کرده است؛ یعنی حالا مقدار amount
میتواند برابر با LIMIT
باشد، در حالی که در منطق اصلی چنین چیزی مجاز نبود.
فرض کنیم در تست های واحد، مقدار amount
را فقط برای اعداد ۳ و ۸ بررسی کرده باشیم. در این حالت، با اینکه تست ها شاخه های مختلف تابع را پوشش میدهند و گزارش “پوشش صددرصدی شاخه” صادر میشود، اما تست جهش بهدرستی شکست میخورد. چرا؟
چون مقدار مرزی (۵ یا حتی ۴) در تست ها بررسی نشده و تغییر منطق شرط توسط تست ها شناسایی نشده است. این یعنی تست ها منطق دقیق مورد نظر برنامه نویس را بهدرستی بیان نکردهاند.
در واقع، فقط با بررسی مقدارهایی مانند ۳ و ۸، نمیتوان بهصورت کامل رفتار تابع را مشخص کرد. برای تعریف دقیق و کامل مشخصات عملکرد تابع، تست ها باید مقدارهایی را نیز در محدوده مرزی مثل ۴ و ۵ پوشش دهند.
ابزارهای تست Mutation در سالیدیتی
برای ارزیابی اثربخشی تست های واحد در قراردادهای هوشمند، ابزارهای مختلفی برای ایجاد جهش های کنترلشده در دسترس هستند. این ابزارها نسخههایی تغییریافته از کد ایجاد میکنند و به توسعهدهندگان کمک میکنند تا نقاط ضعف احتمالی در تست ها را شناسایی کنند.
برخی از ابزارهای شناختهشده در این حوزه عبارتند از:
-
Gambit، توسعهیافته توسط تیم Certora
-
Universal Mutator، ارائه شده توسط sambucha
این ابزارها توانایی تولید جهش های متنوع را دارند، اما در بیشتر موارد، اجرای تست ها و تحلیل نتایج بهصورت دستی انجام میشود. به همین دلیل، استفاده مؤثر از آنها نیازمند آشنایی با نحوه اجرای دقیق تست ها و بررسی رفتارهای غیرمنتظره در کد است.
توسعه دهندگان هنگام انتخاب ابزار، باید به فاکتورهایی مانند پشتیبانی از فریمورکهایی نظیر Foundry، Hardhat یا Truffle، سازگاری با نسخههای جدید سالیدیتی، و میزان پشتیبانی و بهروزرسانی ابزار توجه داشته باشند. همچنین برخی از ابزارهای قدیمی ممکن است دیگر نگهداری نشوند و برای پروژههای جدید مناسب نباشند.
امتیاز جهش (Mutation Score)
در برخی زبان های برنامه نویسی، ابزارهای تست جهش یک امتیاز جهش نیز ارائه میدهند. این امتیاز نشاندهنده درصد جهش هایی است که توسط تست های واحد شناسایی و “حذف” شدهاند. اگر ۱۰۰٪ جهش ها شناسایی شوند، میتوان با اطمینان بالا به تستها اعتماد کرد که در صورت بروز تغییرات ناخواسته یا تصادفی در کد، واکنش مناسبی نشان خواهند داد.
هرچقدر اندازه و پیچیدگی پروژه افزایش یابد، دستیابی به امتیاز کامل تست جهش برای تیم توسعه دشوارتر میشود. اما قراردادهای هوشمند سالیدیتی نسبت به برنامههای بکاند یا فرانتاند سنتی بسیار کوچکتر هستند. از سوی دیگر، باگهای موجود در قراردادهای هوشمند میتوانند تبعات جبرانناپذیر مالی یا امنیتی به همراه داشته باشند.
به همین دلیل، در حوزه قراردادهای سالیدیتی، دستیابی به امتیاز جهش ۱۰۰٪ هدفی منطقی و ارزشمند است. حتی اگر فقط تعداد کمی جهش باقی مانده باشند، باید با دقت کامل مورد بررسی قرار بگیرند، چرا که ممکن است نشاندهنده ضعف جدی در پوشش تستها باشند.
محدودیتهای تست Mutation
اگرچه تست جهش (mutation test) ابزار قدرتمندی برای سنجش کیفیت تستهای واحد محسوب میشود، اما این روش نیز محدودیتهایی دارد:
تست های واحد در اکثر موارد بدون وضعیت (stateless) هستند. بسیاری از منطقهای تجاری در قراردادهای هوشمند بر اساس وضعیت متغیر سیستم عمل میکنند. به همین دلیل، تست جهش بهتنهایی نمیتواند بررسی کند که این منطق ها بهدرستی توسط تست ها پوشش داده شدهاند.
ابزارهای تست جهش میتوانند صدها جهش مختلف ایجاد کنند. با این حال، بهدلیل محدودیت منابع و زمان، معمولاً فقط بخشی از آنها اجرا میشود. این به این معناست که برخی جهشهای مهم، که ممکن بود ضعف تست ها را آشکار کنند، ممکن است هرگز بررسی نشوند.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۶ مرداد ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++C
- ADO.NET
- Adobe Flash
- Ajax
- AngularJS
- apache
- ARM
- Asp.Net
- ASP.NET MVC
- AVR
- Bootstrap
- CCNA
- CCNP
- CMD
- CSS
- Dreameaver
- EntityFramework
- HTML
- IOS
- jquery
- Linq
- Mysql
- Oracle
- PHP
- PHPMyAdmin
- Rational Rose
- silver light
- SQL Server
- Stimulsoft Reports
- Telerik
- UML
- VB.NET&VB6
- WPF
- Xml
- آموزش های پروژه محور
- اتوکد
- الگوریتم تقریبی
- امنیت
- اندروید
- اندروید استودیو
- بک ترک
- بیسیک فور اندروید
- پایتون
- جاوا
- جاوا اسکریپت
- جوملا
- دلفی
- دوره آموزش Go
- دوره های رایگان پیشنهادی
- زامارین
- سئو
- ساخت CMS
- سی شارپ
- شبکه و مجازی سازی
- طراحی الگوریتم
- طراحی بازی
- طراحی وب
- فتوشاپ
- فریم ورک codeigniter
- فلاتر
- کانستراکت
- کریستال ریپورت
- لاراول
- معماری کامپیوتر
- مهندسی اینترنت
- هوش مصنوعی
- یونیتی
- کتاب های آموزشی
- Android
- ASP.NET
- AVR
- LINQ
- php
- Workflow
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس