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

هرم تست یک تصویر مفید برای فکرکردن به سبد تست است: معمولاً تست‌های ریز و سریع بیشتر و تست‌های سرتاسری گران کمترند. اما این تصویر، نسبت جادویی یا قانون معماری نیست. راه درست این است که برای هر ریسک، کوچک‌ترین مرز مسئولی را انتخاب کنیم که مکانیزم خرابی را حفظ می‌کند و fidelity و Oracle کافی دارد.

هرم تست چیست و چه چیزی را ساده می‌کند؟

مایک کوهن در کتاب Succeeding with Agile مدل سه‌لایه Unit، Service و UI را مطرح کرد. توضیح Martin Fowler درباره Test Pyramid نیز آن را راهی برای ساخت یک پرتفوی متعادل از تست‌های خودکار معرفی می‌کند. پیام اصلی ساده است:

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

این مدل در واکنش به suiteهایی محبوب شد که بیشترشان از مسیر UI یا اجرای دستی عبور می‌کردند. ارزش هرم در جهت اقتصادی آن است: شواهد را تا جای ممکن سریع، محلی و ارزان بگیرید، ولی واقعیت مهم را از آزمایش حذف نکنید.

هرم تست چه چیزی نمی‌گوید؟

  • نمی‌گوید همه پروژه‌ها باید نسبت یکسانی داشته باشند؛
  • نمی‌گوید هر Unit Test سریع، پایدار یا ارزشمند است؛
  • نمی‌گوید E2E الزاماً از UI عبور می‌کند؛ یک تست API هم می‌تواند کل مسیر را طی کند؛
  • نمی‌گوید تست‌های امنیت، کارایی، دسترس‌پذیری یا اکتشافی کجا قرار می‌گیرند؛
  • نمی‌گوید کیفیت تست یا قدرت Oracle را چگونه بسنجیم؛
  • نمی‌گوید وابستگی Fake چقدر از رفتار واقعی drift کرده است؛
  • نمی‌گوید کدام ریسک محصول هنوز هیچ شاهدی ندارد.

به همین دلیل Google Testing Blog در مدل SMURF پیشنهاد می‌کند فراتر از شکل، پنج trade-off را ببینیم: Speed، Maintainability، Utilization، Reliability و Fidelity.

نسبت ۷۰/۲۰/۱۰ قانون نیست

در برخی مطالب، ۷۰٪ Unit، ۲۰٪ Integration و ۱۰٪ E2E به‌عنوان نقطه شروع ذکر می‌شود. حتی مطلب قدیمی گوگل درباره کاهش E2E نیز این نسبت را «حدس اولیه» می‌نامد و تصریح می‌کند ترکیب هر تیم متفاوت است. تبدیل این مثال به KPI چند مشکل می‌سازد:

  • با افزودن Unit Testهای کم‌ارزش می‌توان درصد را زیبا کرد؛
  • یک تست داده‌محور ممکن است صد case اجرا کند، اما در داشبورد یک تست شمرده شود؛
  • یک E2E ممکن است ده رفتار را assert کند و با یک شمارش ساده هم‌وزن یک Unit شود؛
  • معماری Frontend، Firmware، Data Pipeline و Microservice مکانیزم‌های خرابی متفاوتی دارند؛
  • نسبت، ریسک پوشش‌داده‌نشده و کیفیت Oracle را پنهان می‌کند.

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

Level، Type، Size، Interface و Lane را جدا کنید

بخش زیادی از اختلاف تیم‌ها درباره هرم، اختلاف واژگان است. سیلابس ISTQB CTFL ۴.۰.۱ Test Level را از Test Type جدا می‌کند. یک تست امنیت می‌تواند در سطح Component یا System اجرا شود؛ API نیز رابط اجراست، نه الزاماً سطح تست.

محور پرسش نمونه
Level/Scope چه مقدار از سیستم زیر تست است؟ Component، Integration، System
Type/Purpose چه ویژگی‌ای را ارزیابی می‌کنیم؟ Functional، Security، Performance، Accessibility
Size/Resource چه منابع و محدودیت‌هایی دارد؟ بدون شبکه، localhost/DB، dependency بیرونی
Interface از کجا سیستم را تحریک و مشاهده می‌کنیم؟ تابع، API، event، CLI، UI
Lane کجا و چند وقت اجرا می‌شود؟ local، pre-commit، PR، nightly، post-deploy

مدل Small/Medium/Large گوگل اندازه را با محدودیت‌های قابل‌اعمال مانند شبکه، پایگاه داده، filesystem و زمان اجرا تعریف می‌کند. این رویکرد برای enforce کردن بودجه مفید است، اما نام داخلی تیم باید دقیقاً توضیح دهد چه boundary و dependencyهایی واقعی یا جایگزین شده‌اند.

مدل SMURF؛ پنج trade-off هر تست

بُعد پرسش عملی خطر بهینه‌سازی افراطی
Speed شاهد در چه مدت به توسعه‌دهنده می‌رسد؟ حذف dependency واقعی و از دست‌دادن خطای مرزی
Maintainability با تغییر رفتار یا معماری، چند تست باید عوض شود؟ abstraction مبهم و تستی که قصدش خوانده نمی‌شود
Utilization CPU، RAM، شبکه، runner و سرویس بیرونی چقدر مصرف می‌شود؟ کاهش fidelity فقط برای ارزان‌شدن اجرا
Reliability آیا شکست، معمولاً مشکل واقعی و قابل‌اقدام است؟ پنهان‌کردن باگ واقعی با retry یا tolerance زیاد
Fidelity آزمایش چقدر مکانیزم واقعی خرابی را حفظ می‌کند؟ محیط عظیم، کند و غیرقابل‌تشخیص

تست Unit معمولاً در چهار بُعد اول بهتر و در fidelity ضعیف‌تر است؛ E2E معمولاً fidelity بیشتری دارد اما گران‌تر است. این «گرایش» است، نه قانون: Unit وابسته به thread و زمان می‌تواند flaky باشد و E2E خوب با داده مستقل و انتظار شرطی می‌تواند پایدار بماند.

اصل کلیدی: کوچک‌ترین مرز مسئول، نه پایین‌ترین لایه ممکن

توصیه رایج «تست را تا پایین‌ترین لایه ممکن ببرید» ممکن است مکانیزم خرابی را حذف کند. اگر ریسک مربوط به constraint واقعی PostgreSQL است، Mock کردن repository و نوشتن Unit Test شاهد کافی نیست. عبارت دقیق‌تر این است:

تست را در کوچک‌ترین boundary بنویسید که علت محتمل خرابی، قرارداد واقعی و Oracle لازم را حفظ می‌کند.

برای انتخاب سطح، این مسیر را طی کنید:

  1. ریسک را نام ببرید: چه خسارتی و برای چه کاربری؟
  2. مکانیزم خرابی را مشخص کنید: منطق، serialization، transaction، مرورگر، شبکه یا پیکربندی؟
  3. مرز لازم را پیدا کنید: کمترین اجزایی که آن مکانیزم را واقعاً اجرا می‌کنند کدام‌اند؟
  4. fidelity را انتخاب کنید: کدام dependency باید واقعی و کدام می‌تواند Double باشد؟
  5. Oracle را تعریف کنید: درست یا غلط بودن را مستقل از implementation چگونه می‌فهمیم؟
  6. بودجه بازخورد را تعیین کنید: این شاهد باید در local، PR، nightly یا post-deploy برسد؟
  7. شکست را قابل‌اقدام کنید: Evidence، owner و طبقه‌بندی failure چیست؟

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

لایه صفر: تحلیل ایستا و بازخورد بدون اجرا

Compiler، type checker، linter، secret scan، dependency scan و برخی قواعد معماری پیش از اجرای برنامه خطا می‌گیرند. این ابزارها معمولاً داخل سه لایه کلاسیک نیستند، ولی ارزان‌ترین بازخورد سبدند. خطایی که با type system قابل‌حذف است نباید فقط در E2E شکار شود.

تحلیل ایستا جای تست رفتار runtime را نمی‌گیرد. Rule باید version، false-positive policy و مالک داشته باشد و نتیجه‌اش مانند هر gate دیگری قابل‌توضیح باشد.

Unit/Small؛ منطق، حالت و invariant

Unit Test برای منطق تصمیم، تبدیل داده، state machine، invariant و edge caseهای فراوان مناسب است. «Unit» لزوماً یک method یا class نیست؛ می‌تواند یک خوشه رفتار منسجم باشد. ارزش اصلی آن سرعت، کنترل و محلی‌بودن failure است.

Unit Test چه چیزی را نباید وانمود کند؟

  • Mock پایگاه داده، سازگاری SQL و schema واقعی را اثبات نمی‌کند؛
  • Mock HTTP، header، TLS و serialization واقعی را اثبات نمی‌کند؛
  • assert روی call count لزوماً نتیجه کسب‌وکار را ثابت نمی‌کند؛
  • coverage بالا، فقدان assertion یا Oracle ضعیف را جبران نمی‌کند.

برای جلوگیری از تست‌های implementation-coupled، boundary و الگوهای suite را در راهنمای کد تست قابل نگهداری ببینید.

Component/Service Test؛ لایه‌ای که اغلب گم می‌شود

یک سرویس یا component را از رابط عمومی‌اش اجرا کنید، اما dependencyهای بیرونی را کنترل کنید. برای مثال، API پرداخت با دیتابیس واقعی ephemeral و PSP Stub اجرا شود. این سطح می‌تواند routing، validation، serialization، transaction و منطق سرویس را با هزینه کمتر از E2E بسنجد.

Component Test پلی میان Unitهای کوچک و سیستم کامل است. در بسیاری از suiteهای ساعت‌شنی، همین لایه غایب است و تیم مجبور می‌شود هر خطای مرزی را با مرورگر پیدا کند.

Integration Test؛ یک برچسب مبهم را قراردادی کنید

به‌جای «Integration» بدون توضیح، boundary را نام ببرید:

  • DB integration: adapter و schema واقعی؛
  • Queue integration: publish/consume، ordering و redelivery؛
  • Filesystem integration: encoding، path و permission؛
  • External API adapter: HTTP client با stub server یا sandbox؛
  • Broad integration: چند سرویس deployشده با شبکه واقعی.

هر دسته باید dependency، reset، timeout و lane مشخص داشته باشد. جزئیات Narrow/Broad، database واقعی و خطاهای مرزی در مقاله تست یکپارچه‌سازی آمده است.

Contract Test؛ سازگاری بدون راه‌اندازی کل جهان

Contract Test بررسی می‌کند provider و consumer درباره request، response، schema، event و قواعد سازگاری توافق دارند. این تست جای E2E را کامل نمی‌گیرد؛ deployment، routing، certificate یا ترکیب پیکربندی ممکن است همچنان خراب باشد. در مقابل، E2E هم معمولاً تمام حالت‌های قرارداد را اقتصادی پوشش نمی‌دهد.

برای Microservice، ترکیب Unit/Component داخل هر سرویس، Contract در مرز تیم‌ها و چند journey سرتاسری معمولاً از یک E2E عظیم قابل‌تشخیص‌تر است. راهبرد کامل این معماری در تست میکروسرویس‌ها از Contract تا E2E توضیح داده شده است.

E2E/System؛ جریان حیاتی، نه جدول همه حالت‌ها

E2E باید یک capability واقعی را از ورودی عمومی تا اثر نهایی بسنجد. لزوماً مرورگری نیست: اجرای API خرید تا ثبت ledger می‌تواند سرتاسری باشد. UI E2E زمانی لازم است که خود interaction مرورگر، routing، rendering، accessibility state یا اتصال Frontend به Backend بخشی از ریسک باشد.

راهنمای Practical Test Pyramid توصیه می‌کند حالت‌های متعدد در لایه پایین پوشش داده شوند و تست سطح بالا فقط ارزشی را بسنجد که پایین‌تر قابل‌اثبات نیست. یک E2E خوب:

  • یک journey حیاتی و outcome کسب‌وکار روشن دارد؛
  • داده و حساب اجرای مستقل دارد؛
  • از sleep و ترتیب وابسته به تست قبلی استفاده نمی‌کند؛
  • artifact، trace و correlation لازم برای تشخیص را ذخیره می‌کند؛
  • edge caseهای منطق را دوباره به جدول عظیم UI تبدیل نمی‌کند.

Best Practices رسمی Playwright نیز بر رفتار قابل‌مشاهده کاربر، ایزولاسیون و locatorهای مقاوم تأکید می‌کند. POM یا retry بدون رفع state leakage و wait نامعتبر، flakiness را درمان نمی‌کند.

تست اکتشافی و انسانی بیرون هرم نیستند؛ محور دیگری‌اند

هرم عمدتاً درباره سبد تست خودکار است. اکتشاف، usability، مصاحبه کاربر و بازبینی بصری را نباید به‌زور لایه چهارم کرد. آن‌ها پرسش‌های متفاوتی درباره unknownها، تجربه و مدل ذهنی می‌پرسند.

نتیجه اجرای خودکار، incident و تغییر محصول باید Charter اکتشافی بسازد؛ یافته اکتشافی تکرارپذیر نیز در مناسب‌ترین سطح به regression test تبدیل شود. برای طراحی Session و Evidence به راهنمای تست اکتشافی ساختاریافته مراجعه کنید.

نمونه عملی ایران: سبد تست Checkout و PSP

در یک فروشگاه ایرانی، قیمت برای کاربر تومان نمایش داده می‌شود، ولی قرارداد PSP مبلغ ریال می‌خواهد. callback ممکن است دیر یا تکراری برسد؛ شبکه timeout بدهد؛ تاریخ گزارش با Asia/Tehran بسته شود و کاربر رقم فارسی وارد کند. یک سبد ریسک‌محور می‌تواند چنین باشد:

ریسک/مکانیزم کوچک‌ترین مرز مسئول dependency/fidelity Oracle Lane
تبدیل تومان به ریال و rounding Unit/Property بدون I/O جدول مستقل و invariant مبلغ local/PR
اعتبارسنجی رقم فارسی و عربی Component API parser و validation واقعی canonical value/error code PR
serialization مبلغ PSP Adapter integration HTTP stub با schema واقعی request contract PR
یکتایی اثر callback تکراری Service + DB integration PostgreSQL/schema واقعی و PSP Stub یک ledger entry برای idempotency key PR/merge
سازگاری API میان Checkout و Payment Contract artifact قرارداد نسخه‌دار consumer expectation/provider verification PR هر دو سرویس
خرید کامل با مرورگر UI E2E سرویس‌های deployشده و PSP sandbox/Stub Paid order + receipt + ledger merge/pre-release
مرز روز تهران و UTC Unit + Component Clock تزریقی و DB واقعی business-date rule PR
TLS، routing و latency واقعی PSP Probe مجاز/Monitoring محیط واقعی با تراکنش مصنوعی امن SLO و پاسخ قرارداد بدون اثر مالی post-deploy/scheduled

داده هر اجرا باید run ID، واحد پول، timezone و cleanup policy داشته باشد. راهنمای مدیریت داده تست برای تولید داده فارسی، ایزولاسیون و retention تکمیل‌کننده این سبد است.

Decision Card؛ چرا این تست در این سطح است؟

برای تست‌های پرهزینه یا بحرانی یک کارت کوتاه در repository نگه دارید:

id: payment-callback-idempotency
risk: duplicate callback creates a second financial effect
failure_mechanism: transaction + unique constraint + message redelivery
boundary: payment service with real PostgreSQL
replaced_dependency: scripted PSP stub
oracle: exactly one ledger credit per idempotency_key
evidence: run_id, callback_id, transaction_id, outbox_event
lane: pull-request
budget_p95: 45s
owner: payments-team

این کارت جلوی بحث‌های سلیقه‌ای «Unit بهتر است یا E2E؟» را می‌گیرد. اگر failure mechanism تغییر کند، سطح تست هم باید دوباره ارزیابی شود.

هم‌پوشانی هدفمند با تکرار بی‌ارزش فرق دارد

یک رفتار حیاتی ممکن است در چند سطح دیده شود، اما هر سطح باید سؤال متفاوتی داشته باشد:

  • Unit همه حالت‌های محاسبه مبلغ را می‌سنجد؛
  • Integration تبدیل domain amount به JSON قرارداد PSP را می‌سنجد؛
  • E2E فقط یک happy path و یک failure حیاتی را تا outcome نهایی می‌سنجد؛
  • Probe بعد از استقرار سلامت routing و configuration واقعی را می‌سنجد.

اگر همان جدول ۴۰حالته در هر چهار سطح تکرار شده است، change amplification بالا می‌رود. اگر فقط Unit دارید و هیچ تستی serialization واقعی را نمی‌بیند، fidelity gap دارید. هدف، پوشش مکمل مکانیزم‌هاست.

شکل سبد در معماری‌های مختلف

Modular Monolith

Unitهای دامنه، Component Test ماژول با دیتابیس واقعی، integration مرز ماژول و چند system journey معمولاً شکل نزدیک به هرم می‌سازد.

Frontend سنگین

منطق خالص کم و تعامل Component زیاد ممکن است میانه را پهن‌تر کند؛ مدل‌هایی مانند Testing Trophy همین تأکید را نشان می‌دهند. این مجوزی برای حذف Unit منطق یا پرکردن suite با UI E2E نیست.

Microservice

هر سرویس سبد کوچک خود را دارد، Contractها مرز تیم‌ها را می‌پوشانند و تنها چند journey میان‌سرویسی باقی می‌ماند. dependency graph و ownership از شمارش تست مهم‌تر است.

Legacy

Hourglass می‌تواند وضعیت گذار باشد: چند characterization/system test ایمنی اولیه می‌دهند و با ایجاد seam، رفتارها به Component/Integration قابل‌تشخیص منتقل می‌شوند. حذف E2E پیش از ساخت safety net پایین‌تر خطرناک است.

Data/ML و سیستم‌های احتمالاتی

تست schema، کیفیت داده، property، drift، reproducibility و ارزیابی آماری ممکن است از سه‌گانه کلاسیک مهم‌تر باشد. Dataset و threshold نیز بخشی از هویت Oracle هستند؛ شمارش ساده Unit/E2E معنای کمی دارد.

چهار ضدالگو و تشخیص ریشه‌ای

بستنی قیفی

نشانه: regression عمدتاً دستی یا UI، بازخورد ساعتی/روزی و triage طولانی. درمان: failureهای پرتکرار را بر اساس مکانیزم به Component/Integration منتقل کنید؛ API setup و داده مستقل بسازید؛ journeyهای تکراری را حذف کنید.

ساعت شنی

نشانه: Unit فراوان و E2E فراوان، اما شکست‌های schema، message و adapter فقط در E2E دیده می‌شوند. درمان: لایه مرزی واقعی و Contract ایجاد کنید.

قلعه Mock

نشانه: Unitها سبزند اما انتشار با ناسازگاری dependency می‌شکند؛ تست‌ها call graph را assert می‌کنند. درمان: Fake/Mock را با contract و adapter integration کالیبره و Oracle را روی outcome متمرکز کنید.

بیابان Fidelity

نشانه: suite سریع است، ولی هیچ تستی image، migration، config، مرورگر یا مسیر deployشده را نمی‌بیند. درمان: کمترین تعداد تست high-fidelity لازم برای ریسک‌های واقعی را اضافه کنید؛ سرعت مطلق هدف نیست.

چگونه سبد موجود را ممیزی کنیم؟

برای هر test target یا گروه همگن، این فیلدها را ثبت کنید:

  • ریسک و capability پوشش‌داده‌شده؛
  • boundary و رابط تحریک؛
  • dependency واقعی، Fake و نسخه قرارداد؛
  • p50/p95 زمان اجرا و مصرف runner؛
  • first-attempt reliability و دفعات retry؛
  • آخرین failure محصولیِ معتبر؛
  • زمان median تا طبقه‌بندی failure؛
  • تعداد فایل‌های تغییرکرده هنگام تغییر رفتار؛
  • lane، owner و وضعیت quarantine؛
  • Oracle و artifact تشخیصی.

سپس یکی از تصمیم‌های Keep، Split، Move down، Add fidelity، Rewrite، Quarantine-with-expiry یا Delete را ثبت کنید. تست سبزی که سال‌ها هیچ failure معناداری نگرفته شاید ارزش پیشگیرانه داشته باشد؛ حذف آن فقط با بررسی ریسک، mutation یا تاریخچه تغییر انجام شود، نه حدس.

انتقال از suite بالاسنگین بدون توقف انتشار

  1. ده failure پرتکرار E2E در سه ماه اخیر را بر اساس Product/Test/Data/Environment/Dependency دسته‌بندی کنید؛
  2. برای هر failure محصولی، کوچک‌ترین boundary حفظ‌کننده مکانیزم را پیدا کنید؛
  3. تست پایین‌تر را بسازید و عمداً نشان دهید همان defect را می‌گیرد؛
  4. E2E را به یک journey کوتاه یا assertion منحصربه‌فرد کاهش دهید؛
  5. API setup، namespace مستقل و evidence استاندارد بسازید؛
  6. تست flaky را با owner و expiry قرنطینه کنید؛ retry دائمی نگذارید؛
  7. فقط پس از اثبات شواهد جایگزین، تست تکراری را حذف کنید.

هر سطح در کدام Lane اجرا شود؟

Lane نمونه محتوا هدف سیاست شکست
Local/pre-commit Static + Unit انتخابی بازخورد هنگام کدنویسی اصلاح قبل از push
Pull Request Unit کامل، Component، Contract و integration کنترل‌شده حفاظت از merge failure قابل‌اقدام و gate روشن
Default branch Integration گسترده‌تر و migration کشف interaction پس از ادغام مالک و توقف promotion
Pre-release/scheduled E2E، compatibility، performance/security منتخب ریسک پرهزینه یا زمان‌بر تصمیم انتشار مبتنی بر ریسک
Post-deploy Smoke، canary، synthetic و monitoring config و runtime واقعی rollback/pause با guardrail

اعداد budget را تیم از SLO بازخورد و ظرفیت runner تعیین می‌کند؛ «همه تست‌ها در هر commit» همیشه اقتصادی نیست. معماری lane و gate در Continuous Testing در CI/CD و جزئیات Artifact/Runner در تست خودکار GitLab CI آمده است.

معیارهایی بهتر از تعداد و درصد لایه‌ها

  • Risk evidence coverage: کدام ریسک‌های اولویت‌دار شاهد معتبر ندارند؟
  • Feedback p50/p95: شواهد هر lane در چه زمانی می‌رسد؟
  • Actionable failure rate: چه سهمی از قرمزها مشکل واقعی و قابل‌مالکیت‌اند؟
  • First-attempt reliability: چند نتیجه بدون retry تکرارپذیر است؟
  • Failure classification time: رسیدن به Product/Test/Data/Environment/Dependency چقدر طول می‌کشد؟
  • Fidelity gap: کدام مکانیزم‌های تولید در هیچ محیطی اجرا نمی‌شوند؟
  • Change amplification: تغییر یک رفتار چند تست و لایه را ویرایش می‌کند؟
  • Quarantine age/exposure: تست قرنطینه چه مدت و چه ریسکی را بی‌حفاظ گذاشته است؟
  • Resource cost: دقیقه runner، CPU/RAM و هزینه dependency به تفکیک lane؛
  • Escaped-defect learning: defect گریخته در کدام مکانیزم و سطح می‌توانست ارزان‌تر دیده شود؟

Coverage کد می‌تواند شکاف اجرای مسیر را نشان دهد، اما صحت assertion و نیاز جاافتاده را ثابت نمی‌کند. Mutation score نیز فقط یکی از شواهد حساسیت suite است و نباید به مسابقه عددی تبدیل شود.

Production حلقه آخر بازخورد است

هیچ محیط pre-production تمام config، ترافیک، dependency و رفتار عملیاتی را کپی نمی‌کند. Google SRE در Testing for Reliability میان تست‌های سنتی و production تمایز می‌گذارد و یادآوری می‌کند pass شدن تست‌ها به‌تنهایی reliability را اثبات نمی‌کند.

Canary، synthetic probe، SLI و incident باید خلأ سبد را آشکار کنند. اگر خطایی فقط در production دیده شد، کورکورانه یک E2E جدید نسازید؛ ابتدا مکانیزم را مشخص کنید و ارزان‌ترین regression test مناسب را پایین‌تر قرار دهید. تست production نیز باید tenant مصنوعی، PII-safe evidence، rate limit، audit، kill switch و blast radius محدود داشته باشد.

برنامه ۳۰روزه بازطراحی هرم تست

هفته اول: زبان مشترک و inventory

Level/Type/Size/Interface/Lane را تعریف کنید. ده جریان پرریسک و ده failure پرهزینه را استخراج و test targets را با زمان، flake، owner و dependency فهرست کنید.

هفته دوم: دو شکاف پرهزینه

یک E2E تکراری را به Component/Integration منتقل کنید و یک fidelity gap را با DB/Contract واقعی بپوشانید. نشان دهید هر تست defect هدف را عمداً می‌گیرد.

هفته سوم: داده و Pipeline

run ID، namespace و cleanup را استاندارد کنید؛ laneها و budgetهای p95 را تعریف کنید؛ Artifact و failure taxonomy یکسان بسازید.

هفته چهارم: سیاست و روند

Decision Card را وارد pull request کنید؛ quarantine با expiry و owner بسازید؛ dashboard را از تعداد تست به Risk Evidence، Feedback، Reliability، Fidelity Gap و Classification Time تغییر دهید.

ضدالگوهای کوتاه

  • هدف‌گذاری سازمانی ۷۰/۲۰/۱۰ بدون تعریف شمارنده و ریسک؛
  • نامیدن هر تست پایگاه داده‌ای به‌عنوان E2E یا هر تست class به‌عنوان Unit؛
  • نوشتن E2E برای همه edge caseهای منطق؛
  • Mock کردن schema، transaction و protocolی که دقیقاً محل ریسک است؛
  • حذف E2E قدیمی پیش از اثبات safety net جایگزین؛
  • استفاده از coverage به‌عنوان release gate یگانه؛
  • retry نامحدود و سبزکردن build با failure غیرقابل‌اقدام؛
  • اجرای موازی روی user، queue یا cache مشترک؛
  • تکرار یک جدول حالت در Unit، API و UI بدون ارزش افزوده؛
  • تکیه به Fake بدون contract و کالیبراسیون؛
  • قرار دادن تست اکتشافی و UX در رقابت عددی با automation؛
  • اندازه‌گیری تعداد تست بدون owner، آخرین ارزش و هزینه نگهداری.

چک‌لیست طراحی سبد تست

  • ریسک، failure mechanism و outcome هر گروه تست مشخص است.
  • Level، Type، Size، Interface و Lane با هم مخلوط نشده‌اند.
  • سطح بر اساس کوچک‌ترین مرز مسئول انتخاب شده است.
  • dependency واقعی/Fake و fidelity gap مستند است.
  • Oracle نتیجه کسب‌وکار را مستقل از implementation می‌سنجد.
  • edge caseها پایین و journeyهای منحصربه‌فرد بالا پوشش داده شده‌اند.
  • داده، state، queue، cache و artifact میان اجراها ایزوله‌اند.
  • هر lane بودجه p95، gate، evidence و owner دارد.
  • quarantine انقضا دارد و retry شکست را پنهان نمی‌کند.
  • معیارها ریسک، feedback، reliability، fidelity و هزینه را نشان می‌دهند.
  • incident و اکتشاف به regression مناسب و تصمیم portfolio برمی‌گردند.
  • هیچ نسبت جهانی به‌عنوان KPI کیفیت استفاده نمی‌شود.

پرسش‌های متداول درباره هرم تست

نسبت مناسب Unit، Integration و E2E چقدر است؟

نسبت جهانی وجود ندارد. از ریسک‌ها و مکانیزم‌های خرابی شروع کنید و برای هر کدام کوچک‌ترین boundary با fidelity کافی را انتخاب کنید. سرعت، قابلیت اعتماد، هزینه و شکاف‌های تولید را بسنجید؛ شکل سبد نتیجه است.

آیا هر تست را باید به پایین‌ترین لایه منتقل کنیم؟

خیر. اگر پایین‌بردن تست مکانیزم خرابی را با Mock حذف کند، شاهد ضعیف‌تر می‌شود. تست باید در کوچک‌ترین مرزی باشد که رفتار واقعی موردریسک را حفظ می‌کند.

آیا E2E همیشه تست UI است؟

خیر. E2E درباره دامنه مسیر است، نه رابط. یک تست API می‌تواند از ورودی عمومی تا دیتابیس، event و dependency را طی کند. UI وقتی لازم است که خود مرورگر و تعامل کاربر بخشی از ریسک باشد.

تست دستی و اکتشافی در کدام لایه هرم قرار می‌گیرد؟

هرم کلاسیک عمدتاً سبد automation را نشان می‌دهد. تست اکتشافی محور مکمل برای unknown، تجربه و مشاهده انسانی است؛ یافته تکرارپذیر آن می‌تواند بعداً در سطح مناسب به regression خودکار تبدیل شود.

هرم بهتر است یا Testing Trophy؟

هیچ شکل به‌تنهایی بهتر نیست. Trophy برای برخی Frontendها بر تست Component/Integration تأکید بیشتری دارد؛ هرم بر اقتصاد تست‌های کوچک. هر دو heuristic هستند. تصمیم باید با ریسک، معماری، SMURF و شواهد واقعی suite توجیه شود.

جمع‌بندی

هرم تست را به‌عنوان قطب‌نما نگه دارید، نه نقشه ساختمانی با ابعاد ثابت. پیام ماندگار آن—بازخورد کوچک و سریع بیشتر، آزمایش سرتاسری گران کمتر—ارزشمند است؛ اما یک سبد بالغ علاوه بر تعداد، fidelity، Oracle، failure mechanism، تشخیص‌پذیری، داده، lane و هزینه را می‌بیند.

از یک نسبت شروع نکنید. ده ریسک اصلی، ده failure پرهزینه و boundaryهای واقعی سیستم را روی میز بگذارید. سپس هر تست را در کوچک‌ترین مرز مسئول قرار دهید، overlap را معنادار کنید و با معیارهای feedback، reliability و risk evidence بهبود را ثابت کنید.

دیدگاهتان را بنویسید