آزمون کد هوش مصنوعی باید نسخه خراب را هم تشخیص دهد

سبز بودن آزمون‌ها، توان تشخیص خطا را ثابت نمی‌کند. با یک تغییر عمدی در نسخه جداگانه، ورودی مرزی، انتظار درست و محدودیت نمره آزمون جهش را بررسی کنید.

آزمون کد هوش مصنوعی باید نسخه خراب را هم تشخیص دهد
نویسنده
تیم ژرف
انتشار
۱۴ مهر ۱۴۰۵
زمان مطالعه
۱۲ دقیقه

دستیار کدنویسی تابع انتخاب اندازه بسته را می‌نویسد و برایش آزمون می‌سازد. همه آزمون‌ها سبزند. بازبین فقط یک علامت را عوض می‌کند: «کوچک‌تر یا مساوی» به «کوچک‌تر» تبدیل می‌شود. حالا سفارش پنج‌تایی بسته اشتباه می‌گیرد، ولی آزمون‌ها هنوز سبزند. برنامه اجرا شده؛ آزمون، اختلاف مهم را ندیده است.

این مثال آموزشی است، نه گزارش رخداد یک مشتری. پرسش این راهنما برای توسعه‌دهنده و مسئول پذیرش کد روشن است: پیش از ادغام کد تولیدشده، چه شاهدی داریم که آزمون‌ها خطای موردنظر را می‌گیرند؟ پاسخ پیشنهادی ژرف، امتحان‌کردن چند تغییر کوچک و معنادار در یک نسخه جداگانه است. هرگز برای این کار سرویس عملیاتی را خراب نکنید.

سبز بودن، پوشش اجرا و تشخیص خطا سه چیز متفاوت‌اند

آزمون سبز می‌گوید اجرا با انتظارهای نوشته‌شده ناسازگار نبوده است. اگر انتظار اشتباه باشد یا چیزی درباره نتیجه نپرسد، همین موفقیت ارزش محدودی دارد. بازبینی باید علاوه بر ورودی، خودِ معیار قبولی را هم بخواند. جمله «آزمون دارد» هنوز نمی‌گوید کدام رفتار حفاظت شده است.

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

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

کد نوشته انسان هم همین مسئله را دارد. دلیل توجه ویژه به کد هوش مصنوعی، سازوکار کار است: وقتی یک دستیار هم پیاده‌سازی و هم انتظار آزمون را از برداشت خودش می‌سازد، امکان دارد یک سوءبرداشت را در هر دو تکرار کند. این تحلیلِ یک مسیر خطاست، نه ادعایی درباره نرخ خطای همه مدل‌ها.

انتظار درست را از نیاز محصول بگیرید، نه از خروجی تابع

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

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

برای هر انتظار، دلیل کوتاهی نگه دارید: بند نیازمندی، نمونه تأییدشده یا قاعده‌ای که مالک محصول روشن کرده است. اگر متن نیازمندی فقط می‌گوید «سفارش کوچک»، از دستیار نخواهید عدد مرزی را حدس بزند. اختلاف بر سر معنای محصول با افزودن آزمون بیشتر حل نمی‌شود؛ ابتدا باید صاحب تصمیم آن را روشن کند.

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

یک ورودی مرزی، نسخه درست و خراب را از هم جدا می‌کند

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

const original = n => n <= 5 ? "small" : "large";
const mutant = n => n < 5 ? "small" : "large";

const initial = [[1, "small"], [6, "large"]];
const improved = [...initial, [5, "small"]];
const passes = (fn, cases) =>
  cases.every(([input, expected]) => fn(input) === expected);

console.log(passes(original, initial));  // true
console.log(passes(mutant, initial));    // true
console.log(passes(original, improved)); // true
console.log(passes(mutant, improved));   // false

دو ورودی اولیه هر دو انتخاب بسته را اجرا می‌کنند، اما برابری با مرز را نمی‌آزمایند. خروجی چهار خط به‌ترتیب درست، درست، درست و نادرست است. این نتیجه محدود، نشان می‌دهد افزودن مورد پنج همان تغییر خاص را تشخیص می‌دهد؛ نه اینکه سه آزمون، تابع را از هر جهت تأیید کرده‌اند.

تعداد اقلامانتظار در قرارداد آموزشیعلت حضور در آزمون
صفررد ورودی در مرز اعتبارسنجیصفر سفارش معتبر نیست
یکبسته کوچککمترین تعداد معتبر
چهاربسته کوچکنزدیک مرز از پایین
پنجبسته کوچکخودِ مرزِ شامل
ششبسته بزرگاولین مقدار پس از مرز
صدبسته بزرگنمونه دور از مرز

جدول، قرارداد کل مسیر را توصیف می‌کند. ردیف صفر باید اعتبارسنج واقعی را صدا بزند؛ انتظار ردشدن از تابع ساده بالا اشتباه است. ورودی متنی، عدد اعشاری و مقدار غایب نیز به آزمون‌های همان مرز تعلق دارند. این تفکیک جلوی ادعای پوشش بخشی را می‌گیرد که اصلاً اجرا نکرده‌ایم.

تغییر عمدی باید کوچک، جدا و قابل بازسازی بماند

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

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

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

پس از افزودن آزمون، نسخه اصلی باید قبول شود و نسخه خراب با همان انتظار معنادار شکست بخورد. تغییر انتظار تا جایی که نسخه خراب هم قبول شود، مسئله را حل نکرده است. در پایان، کپی تغییریافته را کنار بگذارید و تفاوت نهایی را بخوانید: فقط اصلاح واقعی و آزمون ماندگار باید وارد درخواست ادغام شوند. راهنمای بازبینی کد و DevSecOps جای این شاهد را کنار دیگر کنترل‌های انتشار توضیح می‌دهد.

هر نسخه زنده، الزاماً یک آزمون گمشده نیست

اگر نسخه تغییریافته زنده ماند، اول بپرسید آیا رفتارش در دامنه مجاز واقعاً متفاوت است؟ در مثال ما، برای عدد صحیح مثبت، شرط «کوچک‌تر از شش» همان تصمیم «کوچک‌تر یا مساوی پنج» را می‌دهد. آزمونی که تنها به رفتار قراردادی نگاه می‌کند نباید مجبور شود بین این دو فرق بگذارد.

راهنمای نسخه‌های معادل در Stryker توضیح می‌دهد چرا بعضی تغییرها رفتار قابل مشاهده را عوض نمی‌کنند و رسیدن به نمره صد همیشه هدف مناسبی نیست. ادعای معادل‌بودن باید با دامنه ورودی و اثرهای قابل مشاهده توجیه شود؛ «فعلاً مورد نقض پیدا نکردیم» اثبات برابری نیست.

در مقابل، تبدیل شرط به «کوچک‌تر از پنج» برای ورودی پنج فرق واقعی دارد. اینجا یک نمونه متمایزکننده داریم و آزمون مرزی توجیه دارد. اگر کد موردنظر اصلاً اجرا نشده، مسئله رسیدن به مسیر است. اگر اجرا شده ولی اختلاف خروجی به انتظار آزمون نمی‌رسد، باید مقدار یا اثر درست را مشاهده کنیم.

آنچه بررسی نشان می‌دهداقدام بعدی پیشنهادی
اختلاف رفتاری در ورودی مجاز داریمآزمون متکی به نیازمندی اضافه کنید
مسیر تغییریافته اجرا نشدهورودی یا اتصال آزمون به مسیر را اصلاح کنید
اختلاف در خروجی دیده می‌شود ولی آزمون نمی‌پرسدانتظار معنادار بنویسید
تغییر در دامنه قرارداد واقعاً معادل استاستدلال و دامنه را ثبت کنید؛ دنبال نمره مصنوعی نروید
اجرا به علت ابزار یا محیط خراب شدهمحیط آزمون را تعمیر کنید؛ نتیجه را نامعلوم نگه دارید
نیازمندی دو برداشت داردپذیرش را تا روشن‌شدن تصمیم محصول متوقف کنید

شبیه‌ساز وابستگی نباید تمام نتیجه را از پیش تعیین کند

فرض کنید آزمون انتخاب بسته، خودِ انتخاب‌گر را با تابعی جایگزین کند که همیشه «کوچک» برمی‌گرداند. انتظار «کوچک» قبول می‌شود، ولی هیچ شاهدی درباره پیاده‌سازی واقعی نداریم. جایگزین آزمایشی برای مهار یک وابستگی مفید است؛ حذف همان رفتاری که قصد سنجیدنش را داریم، آزمون را تهی می‌کند.

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

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

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

ورودی‌های بیشتر وقتی مفیدند که خاصیت درستی را بسنجند

پس از روشن‌شدن چند نمونه مرزی، آزمون مبتنی بر خاصیت کمک می‌کند ورودی‌های متنوع‌تری بررسی شوند. برای بسته‌بندی می‌توان گفت افزایش تعداد معتبر نباید بسته بزرگ را به کوچک تبدیل کند. اما تابعی که همیشه «بزرگ» می‌دهد نیز این خاصیت را رعایت می‌کند. پس خاصیت کلی بدون نمونه‌های معلوم، کافی نیست.

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

راهنمای دامنه و توزیع در Hypothesis میان مجموعه ورودی‌های قابل تولید و شیوه انتخاب نمونه‌ها فرق می‌گذارد. محدودکردن بی‌دلیل دامنه ممکن است دقیقاً ورودی خطاساز را حذف کند. همچنین هدف جست‌وجوی خطا با بازنمایی فراوانی واقعی ترافیک یکی نیست؛ تعداد شکست‌های این آزمایش را نرخ خطای کاربران گزارش نکنید.

برای اجرای گران، بودجه زمان و تعداد نمونه داشته باشید، ولی محدودیت‌ها را در گزارش پنهان نکنید. یک نمونه شکست را به آزمون بازتولیدپذیر تبدیل کنید تا به شانس اجرای بعد وابسته نماند. دستیار می‌تواند ورودی پیشنهاد دهد؛ بازبین باید همچنان دلیل صحیح یا ناصحیح بودن خروجی را مستقل بررسی کند.

نمره جهش را همراه صورت و مخرج بخوانید

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

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

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

برای شروع، چند بخش کم‌حجم و پُرپیامد را انتخاب کنید. این انتخاب پیشنهادی ژرف است، نه آستانه عمومی کیفیت: شرط‌های پذیرش، محاسبه مرزی و جلوگیری از اثر ناخواسته. اجرای کل مخزن ممکن است هزینه زیادی بسازد. کوچک‌کردن دامنه مجاز است؛ گزارش نتیجه کوچک به‌عنوان تأیید کل سامانه مجاز نیست.

شاهد پذیرش باید بعد از تغییر بعدی هم معنا داشته باشد

بسته بازبینی را کوتاه و قابل پیگیری نگه دارید: قاعده محصول، تغییر عمدی، ورودی متمایزکننده، انتظار درست و نتیجه اجرای نسخه اصلی و خراب. برای موردهای معادل یا حل‌نشده نیز دلیل و مسئول تصمیم بنویسید. این سند، جای مطالعه کد را نمی‌گیرد؛ به بازبین نشان می‌دهد کدام خطای محتمل واقعاً بررسی شده است.

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

پس از تغییر نیازمندی، خودِ آزمون‌ها دوباره موضوع بازبینی می‌شوند. اگر ظرفیت بسته کوچک شش شد، آزمون پنج هنوز مفید است اما دیگر مرز جدید را نمایندگی نمی‌کند. راهنمای کنترل تغییر و انتشار تدریجی این شاهد را به نسخه مشخص پیوند می‌دهد؛ نتیجه قدیمی، مجوز دائمی برای نسخه تازه نیست.

موارد زنده مهم، ناپایداری اجرا، زمان بررسی و خطاهایی را که بعداً از آزمون عبور کرده‌اند پیگیری کنید. هدف، افزودن هزار آزمون هم‌نظر نیست. هنگام پذیرش کد باید بتوانیم یک جمله مشخص بگوییم: «این رفتار را لازم داریم، این تغییر آن را خراب می‌کند، و این آزمون همان خرابی را تشخیص می‌دهد.»

یادداشت منابع — بازبینی ۶ اکتبر ۲۰۲۶

این راهنما بر مستندات رسمی زیر تکیه دارد. Coverage.py هنگام بررسی نسخه ۷٫۱۶٫۲ با تاریخ انتشار ۲۷ سپتامبر ۲۰۲۶ را نشان می‌داد؛ مستندات Hypothesis نسخه ۶٫۱۶۸٫۵ و Python نسخه ۳٫۱۴٫۸ بودند. صفحات Stryker تاریخ انتشار مشخصی ندارند و مرجع زنده‌اند. مثال، جدول تصمیم و روش پذیرش، تحلیل و پیشنهاد مهندسی ژرف‌اند؛ ادعای آزمایش محصول مشتری یا تضمین امنیت نیستند.

#کدنویسی با هوش مصنوعی#آزمون نرم‌افزار#آزمون جهش#بازبینی کد#کیفیت مهندسی

مطالب مرتبط

این ابزار را در تیم خودتان راه بیندازید

اگر می‌خواهید ایجنت‌های هوش مصنوعی و اتوماسیون این راهنما را در تیم نرم‌افزار یا فرایندهای سازمانتان به کار بگیرید، از یک آزمایش کوچک شروع کنید که بتوانید نتیجه‌اش را بسنجید.