صفحه ۱۱ از ۴۹، درس‌ها

درس 10. تنها اجرای کامل پایپ‌لاین راستی‌آزمایی واقعی محسوب می‌شود

متنی ۱۰ دقیقه به‌روز شده در ۱۰ آبان ۱۴۰۵

در این صفحه
  1. نقاط کور آزمون‌های واحد
  2. آزمون سرتاسری نه‌تنها نتایج، بلکه رفتار را تغییر می‌دهد
  3. هرم آزمون و ارتقای بازخورد بررسی
  4. مفاهیم اصلی
  5. چگونه این کار را انجام دهیم
  6. موردی از دنیای واقعی
  7. نکات کلیدی
  8. منابع بیشتر
  9. تمرین‌ها

Code examples for this lecture: code/ Hands-on practice: Project 05. Let the agent verify its own work

شما از ایجنت (agent) می‌خواهید قابلیت خروجی فایل را به یک برنامهٔ Electron اضافه کند. ایجنت مؤلفهٔ رندرکننده (renderer)، اسکریپت preload، و منطق لایهٔ سرویس را می‌نویسد. آزمون‌های واحد برای تک‌تک مؤلفه‌ها پاس می‌شوند. ایجنت می‌گوید «تمام شد». شما واقعاً روی دکمهٔ خروجی کلیک می‌کنید — قالب مسیر فایل اشتباه است، نوار پیشرفت واکنشی نشان نمی‌دهد، و استخراج فایل‌های بزرگ نشت حافظه دارد. پنج نقص در مرز میان مؤلفه‌ها، و آزمون‌های واحد حتی یکی از آن‌ها را نگرفتند.

هر بخش به‌تنهایی «درست» به‌نظر می‌رسد، اما مشکلات دقیقاً زمانی که به هم متصل می‌شوند پدیدار می‌گردند. هرم آزمون گوگل به ما می‌گوید که پایهٔ بزرگی از آزمون‌های واحد ضروری است، اما توقف در همان سطح بدان معناست که شما به‌طور سیستماتیک مشکلات تایجنت میان مؤلفه‌ها را از دست خواهید داد. برای ایجنت‌های کدنویسی هوش مصنوعی این مشکل حادتر است، زیرا ایجنت‌ها تمایل دارند تنها سریع‌ترین آزمون‌ها را اجرا کنند و سپس اتمام کار را اعلام نمایند. تنها آزمون سرتاسری (end-to-end) می‌تواند فقدان نقص‌های سطح سیستم را اثبات کند.

نقاط کور آزمون‌های واحد#

فلسفهٔ طراحی آزمون‌های واحد جداسازی است: ماک کردن (mocking) وابستگی‌ها، و تمرکز بر واحد تحت آزمون. این فلسفه آزمون‌های واحد را سریع و دقیق می‌سازد، اما نقاط کور سیستماتیک نیز ایجاد می‌کند. هر ماژول در انزوا عملکردی بی‌نقص دارد، با این حال دسته‌های زیر از مشکلات تنها زمانی نمایان می‌شوند که همه‌چیز واقعاً در کنار هم اجرا شود:

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

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

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

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

آزمون سرتاسری نه‌تنها نتایج، بلکه رفتار را تغییر می‌دهد#

این چیزی است که افراد زیادی نادیده می‌گیرند: وقتی ایجنت می‌داند کارش توسط آزمون‌های سرتاسری اعتبارسنجی خواهد شد، رفتار کدنویسی آن تغییر می‌کند:

  1. در نظر گرفتن تایجنت میان مؤلفه‌ها: هنگام نوشتن کد شروع به پرسیدن این می‌کند که «این اینترفیس چگونه به بالادست متصل می‌شود؟» به‌جای اینکه صرفاً روی یک تابع منفرد در انزوا تمرکز کند.
  2. احترام به مرزهای معماری: در سیستم‌هایی با محدودیت‌های معماری، آزمون‌های سرتاسری ایجنت را مجبور به پیروی از قواعد مرزی می‌کنند.
  3. مدیریت مسیرهای خطا: آزمون‌های سرتاسری معمولاً شامل سناریوهای شکست هستند، که ایجنت را به سمت تفکر دربارهٔ مدیریت استثناها سوق می‌دهد.

هرم آزمون و ارتقای بازخورد بررسی#

flowchart TB
    subgraph Unit["Unit tests only check isolated parts"]
    U1["Renderer tests"]
    U2["Preload tests"]
    U3["Service tests"]
    end

    subgraph E2E["E2E runs through the real system"]
    R["Click renderer button"] --> P["Preload bridge"]
    P --> S["Service layer"]
    S --> F["File System / OS"]
    F --> Result["Actual exported file"]
    end
flowchart LR
    Review["Review feedback:<br/>renderer cannot import fs directly"] --> Rule["Add a direct fs import check"]
    Rule --> Message["Tell the agent in the error message<br/>to move file access to preload"]
    Message --> Harness["Add this check to the harness"]
    Harness --> Stronger["Next violation fails immediately"]

شرکت OpenAI در تجارب مهندسی Codex خود تأکید می‌کند: پیام‌های خطایی که برای ایجنت‌ها نوشته می‌شوند باید حاوی دستورالعمل‌های اصلاح باشند. به‌جای نوشتن "Direct filesystem access in renderer"، بنویسید: "Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api." این کار قواعد معماری را به یک حلقهٔ تصحیح خودکار تبدیل می‌کند. پیام‌های خطا نه‌تنها به شما می‌گویند «چه چیزی خراب شد» — بلکه می‌گویند «چگونه اصلاح شود»، و به ایجنت امکان می‌دهند به‌طور خودکار خود را تصحیح کند.

مفاهیم اصلی#

  • نقص‌های مرز مؤلفه‌ها: مؤلفهٔ الف و ب هرکدام آزمون‌های واحد خود را پاس می‌کنند، اما تایجنت آن‌ها رفتاری نادرست پدید می‌آورد. این همان دسته‌ای از مشکلات است که آزمون‌های سرتاسری در گرفتن آن‌ها مهارت دارند.
  • شیب کفایت آزمون: نقص‌های قابل‌کشف با آزمون‌های واحد <= نقص‌های قابل‌کشف با آزمون‌های ادغام <= نقص‌های قابل‌کشف با آزمون‌های سرتاسری. قابلیت کشف با هر لایه افزایش می‌یابد.
  • قواعد اعمال مرزهای معماری: تبدیل قواعد اسناد معماری (مانند «فرایند رندرکننده نباید مستقیماً به فایل‌سیستم دسترسی داشته باشد») به بررسی‌های اجرایی و خودکار — گذر از «نوشته‌شده روی کاغذ» به «در حال اجرا در CI».
  • ارتقای بازخورد بررسی (Review Feedback Promotion): تبدیل کامنت‌های تکراری بازبینی کد به آزمون‌های خودکار. هر بار که دسته‌ای جدید از مشکلات تکراری یافت می‌شود، قاعده‌ای بیفزایید، و هارنس (harness) به‌طور خودکار نیرومندتر می‌شود.
  • پیام‌های خطای ایجنت‌محور: پیام‌های شکست نباید صرفاً بیانگر این باشند که «چه مشکلی رخ داد» بلکه باید دقیقاً به ایجنت بگویند چگونه آن را رفع کند، تا شکست آزمون به حلقه‌ای از بازخورد خوداصلاح‌گر بدل شود.

چگونه این کار را انجام دهیم#

0. تعریف مرزهای معماری پیش از نوشتن آزمون‌های E2E#

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

تجربهٔ OpenAI: برای پایگاه کدهایی که توسط ایجنت تولید می‌شوند، محدودیت‌های معماری باید از روز نخست به‌عنوان پیش‌نیازهای اولیه برقرار شوند — نه چیزی که بعد از رشد تیم به آن فکر شود. دلیل آن سرراست است: ایجنت‌ها الگوهای موجود در مخزن (repository) را کپی می‌کنند، حتی زمانی که آن الگوها ناسازگار یا مادون بهینه باشند. بدون محدودیت‌های معماری، ایجنت‌ها با هر نشست انحراف بیشتری وارد می‌کنند.

شرکت OpenAI از یک «معماری دامنه‌ای لایه‌بندی‌شده» بهره گرفت، که در آن هر دامنهٔ کسب‌وکار به لایه‌های ثابتی تقسیم می‌شود: Types -> Config -> Repo -> Service -> Runtime -> UI. وابستگی‌ها اکیداً رو به جلو جریان می‌یابند، و دغدغه‌های بین‌دامنه‌ای از طریق اینترفیس‌های صریح Providers وارد می‌شوند. هر وابستگی دیگر ممنوع است و به‌صورت مکانیکی از طریق لینتینگ سفارشی اعمال می‌گردد.

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

منبع: OpenAI: Harness engineering: leveraging Codex in an agent-first world

1. هارنس باید شامل یک لایهٔ سرتاسری باشد#

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

## Validation Hierarchy
- Level 1: Unit tests (Must pass)
- Level 2: Integration tests (Must pass)
- Level 3: End-to-end tests (Must pass when cross-component changes are involved)
- Skipping any required level = Not Complete

2. تبدیل قواعد معماری به بررسی‌های اجرایی#

هر قید معماری باید دارای یک آزمون یا قاعدهٔ لینت متناظر باشد:

# Check whether the renderer process directly calls Node.js APIs
grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer"

3. طراحی پیام‌های خطای ایجنت‌محور#

پیام‌های شکست باید حاوی سه عنصر باشند: چه چیزی خراب شد، چرا، و چگونه رفع شود:

ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12
WHY: Renderer process has no access to Node.js APIs for security
FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile()

4. استقرار فرایند ارتقای بازخورد بررسی#

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

موردی از دنیای واقعی#

وظیفه: پیاده‌سازی قابلیت خروجی فایل در یک برنامهٔ Electron. شامل UI فرایند رندرکننده، پروکسی فایل‌سیستم اسکریپت preload، و تبدیل داده‌های لایهٔ سرویس.

فاز آزمون واحد: آزمون‌های مؤلفهٔ رندرکننده (پاس شدند، عملیات فایل ماک شد)، آزمون‌های اسکریپت preload (پاس شدند، فایل‌سیستم ماک شد)، آزمون‌های لایهٔ سرویس (پاس شدند، منبع داده ماک شد). ایجنت اتمام کار را اعلام می‌کند.

نقص‌های آشکارشده توسط آزمون‌های سرتاسری:

نقص شرح آزمون واحد سرتاسری (E2E)
عدم تطابق اینترفیس قالب ناسازگار مسیر فایل کشف نشد گرفته شد
انتشار وضعیت پیشرفت استخراج از طریق IPC به UI ارسال نشد کشف نشد گرفته شد
نشت منابع هندل‌های استخراج فایل‌های بزرگ آزاد نشدند کشف نشد گرفته شد
مشکل دسترسی‌ها دسترسی‌های متفاوت در محیط پکیج‌شده کشف نشد گرفته شد
انتشار خطا استثناهای لایهٔ سرویس به لایهٔ UI نرسیدند کشف نشد گرفته شد

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

نکات کلیدی#

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

منابع بیشتر#

تمرین‌ها#

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

  2. خودکارسازی قواعد معماری: یک قید معماری از پروژهٔ خود انتخاب کرده و آن را به یک بررسی اجرایی (همراه با پیام خطای ایجنت‌محور) تبدیل کنید. آن را در هارنس ادغام کرده و اثربخشی‌اش را با یک وظیفهٔ پایه راستی‌آزمایی نمایید.

  3. ارتقای بازخورد بررسی: یک نوع کامنت تکراری را از تاریخچهٔ بازبینی کد پیدا کرده و با دنبال کردن فرایند پنج‌مرحله‌ای آن را به یک بررسی خودکار تبدیل کنید. فراوانی بروز آن دسته مشکل را قبل و بعد از ارتقا مقایسه نمایید.