تصور کنید یک برنامهنویس با افتخار یک درخواست ادغام (PR) شامل ۴۷ فایل را ارائه میدهد که ۳۸ تست جدید را به پروژه اضافه کرده و پوشش کد (Coverage) را از ۶۱٪ به ۷۴٪ رسانده است. روی کاغذ، همه چیز عالی به نظر میرسد. اما وقتی کد اصلی تولیدی (Production Code) حذف میشود، تستها همچنان سبز میمانند؛ این یعنی تستها در واقع هیچ منطقی را بررسی نمیکردند و صرفاً بازتابی از پیشفرضهای مدل هوش مصنوعی بودند.
این پدیده که در گزارش ۱۴ سپتامبر ۲۰۲۶ در وبسایت dev.to منتشر شد، یک نقطه کور بحرانی در توسعه نرمافزار با کمک هوش مصنوعی را افشا میکند. در واقع، وقتی یک عامل (Agent) — شبیه به دستیاری که دستورات را سریع اجرا میکند اما لزوماً آنها را نمیفهمد — هم کد اصلاحی و هم تست را بنویسد، تست دیگر یک سند مستقل برای اثبات صحت کد نیست، بلکه نسخهی دومی از همان خطای احتمالی است که فقط با سینتکس متفاوتی نوشته شده است. این موضوع با باورهای غلط در ارزیابی مدلهای هوش مصنوعی همسو است که در آن تستهای سبز اغلب تنها آینهای از توهمات مدل هستند.
همانطور که در تحلیل قبلی ما دربارهی اینکه چگونه ۱۰۲۲ تست سبز نتوانستند حفرههای امنیتی بحرانی در Traceguard 1.6.0 را شناسایی کنند دیدیم، اعتماد کورکورانه به تستهای سبز میتواند منجر به فجایع فنی شود. این ریسک در گردشکارهای عاملمحور (Agentic) به دلیل نبود استقلال بین نویسنده و بازبین، سیستماتیک شده است و تأیید میکند که چرا تستهای سبزِ عاملهای هوش مصنوعی تضمینی برای صحت کد نیستند و نباید تنها دلیل ادغام کد باشند.
پنج الگوی تستهای «توخالی»
طبق تحلیل dev.to، پنج الگوی رایج باعث ایجاد این مجموعههای تست فریبنده میشوند. اینها باگهای خاص هوش مصنوعی نیستند، اما عاملها آنها را در حجم بالا و با اعداد پوشش (Coverage) بالا تولید میکنند:
- ترجیح حقیقت بر مقدار (Truthiness over Value): عاملها اغلب از
assert resultاستفاده میکنند، در حالی که نتیجه یک دیکشنری، یک لیست یا یک شیء پاسخ مدل است. چون در پایتون تقریباً هر چیزی بهجزNoneبه عنوان «درست» (True) ارزیابی میشود، تست حتی اگر دادههای داخلی کاملاً غلط باشند، پاس میشود. - شبیهسازیِ شبیهساز (Mocking the Mock): تست تابعی را که باید بررسی کند، ماک (Mock) — شبیه به جایگزین کردن یک قطعه واقعی با یک مدل پلاستیکی برای تست — میکند و سپس بررسی میکند که آیا آن ماک با دقیقاً همان آرگومانهایی که خودِ تست ارسال کرده، فراخوانی شده است یا خیر. در این حالت حتی اگر کل کد اصلی را حذف کنید، تست همچنان سبز میماند چون فقط تعامل با ماک را میسنجد.
- استثناهای بیش از حد گسترده (Overly Wide Exceptions): استفاده از
pytest.raises(Exception)بسیار کلی است و نمیتواند به عنوان مدرک صحت عمل کند. ممکن است تست به دلیل یکKeyErrorناشی از یک غلط املایی پاس شود، نه به دلیل خطای دامنه (Domain Error) خاصی که در مستندات PR ذکر شده است. - کپیبرداری از ثابتها (Constant Copying): عاملها مقادیر را مستقیماً از پیادهسازی به بخش ادعا (Assertion) کپی میکنند. برای مثال، اگر در Diff کد عبارت
BASE_FEE = 1187دیده شود، عامل مینویسدassert total == 1187. این کار در واقع کد کردن یک عدد ثابت است، نه بررسی یک قانون تجاری (Business Rule). - اسنپشاتهای معیوب (Buggy Snapshots): ثبت یک خروجی جدید به عنوان تشخیصدهنده رگرسیون، در واقع باگ را منجمد میکند. اگر اسنپشات توسط همان تابع معیوب تولید شده باشد، تست صرفاً تضمین میکند که باگ همچنان پابرجا است و تغییر نمیکند.
متدولوژی دو دقیقهای برای بازبینی
برای مقابله با این وضعیت، بازبینها باید از یک «worktree موقت» (throwaway worktree) استفاده کنند تا تستها را پیش از خواندن حتی یک خط ادعا (Assertion)، ایزوله کنند. این فرآیند شامل دستورات گیت زیر است:
۱. git fetch origin pull/1234/head:pr-1234
۲. git worktree add ../review-pr-1234 pr-1234
۳. cd ../review-pr-1234
۴. git log --oneline origin/main..HEAD (برای شناسایی کامیتهای مربوط به کد اصلی)
۵. git revert --no-commit <fix-sha> (برگرداندن فقط کد اصلی و نگه داشتن تستها)
۶. pytest -q tests/test_billing.py (اجرای تستهای جدید PR)
۷. git reset --hard && git worktree remove --force ../review-pr-1234
تفسیر نتایج آزمایش
بازبینها باید نتایج این آزمایش را صادقانه تحلیل کنند تا کیفیت PR را بسنجند:
- سبز پس از بازگشت کد: تستهای جدید هیچ محدودیتی برای رفتار جدید ایجاد نمیکنند. شما باید آنها را همراه با کد اصلی برگردانید یا آنها را بازنویسی کنید تا زمانی که در نبود کد، قرمز شوند.
- قرمز در تستهای غیرمرتبط: PR وضعیت مشترک (Shared State) را تغییر داده است. در این حالت، این تغییرات باید هدف اصلی بازبینی قرار گیرند.
- قرمز در تستهای مورد نظر: تستها واقعاً در حال انجام کار هستند و منطق را میسنجند. تنها در این حالت است که باید کیفیت آنها را بررسی کنید.
اولویتبندی و الزامات اثبات
گزارش مذکور یک ترتیب برای تریاژ (Triage) ارائه میدهد تا مشخص شود به چه چیزی اعتماد کنیم و چه چیزی را برگردانیم. این یک قانون Lint نیست، بلکه راهنمایی برای مدیریت زمان بازبینی است:
- حذف فوری: ادعاهای
assert x is not Noneدر مسیرهای جدید (مگر اینکه موردی که منجر بهNoneمیشود ارائه شده باشد)، تستهایی که تابعی را که تست میکنند ماک میکنند، و استفاده ازtime.sleepپیش از ادعاها (که باید با انتظارهای قطعی یا تزریق ساعت جایگزین شوند). - بازنویسی: عبارت
pytest.raises(Exception)باید به یک نوع استثنای نامگذاری شده تغییر یابد. مقادیر ادعایی که با یک ثابت جدید مطابقت دارند، باید با مقادیر دستی (Literals) بر اساس مشخصات فنی (Spec) جایگزین شوند. - تأیید و بررسی: فیکسرهایی (Fixtures) که بر اساس شکل کد جدید ماک شدهاند، نیاز به بررسی قرارداد (Contract Check) در برابر اسکیمای واقعی دارند. حداقل یک تست باید از مسیر فراخوانی واقعی (Real Call Path) عبور کند.
- اعتماد محدود به رگرسیون: اسنپشاتهای ثبت شده از خروجیهای جدید را فقط برای رگرسیون قبول کنید. این موارد نیاز به خواندن انسانیِ تفاوتهای اسنپشات (Snapshot Diff) دارند.
کاوش جهش خودکار (Automated Mutation Probing)
برای PRهای بزرگ، نویسنده پیشنهاد میکند از «تست جهش» (Mutation Testing) در محدوده تغییرات (Diff) استفاده شود. اجرای کامل تست جهش روی یک مجموعه بزرگ بسیار کند است و برای بازبینی مناسب نیست. در عوض، این روش Diff یکپارچه را میخواند و در هر خط کد اضافه شده، یک عملگر را تغییر میدهد (مثلاً تغییر == به !=، <= به <، >= به >، + به - یا and به or).
اگر یک جهش «زنده» بماند — یعنی با وجود تغییر در منطق، تستها همچنان سبز باشند — آن خط «لنگر نشده» (Unpinned) است. برای مثال اگر if retries <= MAX به if retries < MAX تغییر کند و تستها همچنان پاس شوند، یعنی تستهای PR آن مرز منطقی را محدود نمیکنند.
گزارش پیشنهاد میکند از لایه رایگان و گزینه سرور رایگان MonkeyCode برای اجرای این کاوشها و تریاژ بازماندگان استفاده شود. این کار به بازبینها اجازه میدهد بدون صرف توکنهای گرانقیمت برای کارهای روتین بازبینی و بدون درگیر کردن سختافزار لپتاپ خود، تولید گونهها و تریاژ را انجام دهند. این رویکرد در واقع پیادهسازی عملی شواهد اجرایی در برابر اعتماد کورکورانه است تا توهمات کدنویسی هوش مصنوعی با دادههای واقعی سنجیده شوند.
محدودیتها و ریسکها
این رویکرد جهانی نیست و در سناریوهای زیر توصیه نمیشود:
- مخازن کوچک یا بدون تست: اگر PR تنها منبع پوشش کد است، بررسی «ابتدا برگردان» اطلاعات زیادی نمیدهد؛ در این حالت باید خود تابع را بخوانید.
- تستهای ناپایدار (Flaky): تستهای ناپایدار باعث میشوند جهشها به صورت تصادفی لنگر شده یا نشده به نظر برسند. ابتدا باید ناپایداریها را رفع کنید.
- مونوریپوهای عظیم: حتی با محدود کردن محدوده، اگر یک فایل تست چهار دقیقه زمان ببرد، این پیمایش گران تمام میشود.
- تغییرات غیرکدی: تغییرات در اسکما، پیشفرضهای پیکربندی و Feature Flagها به ندرت به صورت خطوط کد اضافه شده ظاهر میشوند و با این روش شناسایی نمیشوند.
- نبود بودجه برای Revert: این روش فرض میکند که شما میتوانید با خیال راحت یک شاخه PR را به صورت محلی Checkout کنید.
چکلیست پیش از تأیید
پیش از تأیید یک PR که توسط عامل نوشته شده است، این توالی را دنبال کنید:
۱. تغییرات تولیدی را Revert کنید؛ تأیید کنید که تستهای جدید شکست میخورند.
۲. تستهای جدید را برای عبارتهای raises(Exception)، sleep() و is not None جستوجو (Grep) کنید.
۳. برای هر Mock، بپرسید اگر این شبیهساز غلط باشد، چه چیزی در سیستم میشکند.
۴. کاوش جهش محدود به Diff را روی تستهای اضافه شده توسط PR اجرا کنید.
۵. PR را به سه دسته تقسیم کنید: «همین حالا برگردان»، «ادعاها را بازنویسی کن» و «به عنوان پوشش رگرسیون اعتماد کن».
۶. تنها پس از این مراحل، Diff کد تولیدی را خط به خط بخوانید.
در نهایت، یک مجموعه تست سبز که توسط همان عاملی نوشته شده که کد اصلاحی را نوشته است، یک «فرضیه» است، نه یک «حکم قطعی». درصد پوشش کد یک معیار توخالی (Vanity Metric) است اگر ادعاها توخالی باشند. این تغییر در فرهنگ بازبینی، تمرکز را از «آیا تستها پاس میشوند؟» به «کدام خط باید غلط باشد تا این تست شکست بخورد؟» منتقل میکند.




گفتگو