آخرین بازبینی فنی: مرداد ۱۴۰۵

یک شرط ساده می‌تواند میلیون‌ها تومان خسارت بسازد: برنامه‌نویس به‌جای amount <= 50_000_000 می‌نویسد amount < 50_000_000. تست مقدار میانی هیچ مشکلی نمی‌بیند؛ اما دقیقاً روی سقف مجاز، تراکنش درست رد می‌شود. تحلیل مقدار مرزی یا BVA برای شکار همین جابه‌جایی، حذف یا اشتباه در شمول مرز طراحی شده است.

با این حال، «حداقل، حداکثر و چند عدد اطرافشان» هنوز یک طراحی تست کامل نیست. باید بدانیم پارتیشن چیست، مرز شامل است یا نه، کوچک‌ترین گام داده کدام است، Oracle چه می‌گوید و چند ورودی چگونه با هم تعامل دارند. در این آموزش، BVA دو‌مقداری و سه‌مقداری را طبق ISTQB ۴.۰.۱ یاد می‌گیرید، تفاوت Robust BVA با Robustness Testing و Error Guessing را می‌بینید و یک مثال ریالی قابل اجرا می‌سازید.

خلاصه عملی: ابتدا پارتیشن‌های هم‌ارزِ مرتب را مشخص کنید. برای هر مرز، در BVA دو‌مقداری خود مرز و نزدیک‌ترین همسایه در پارتیشن مجاور را بگیرید؛ در BVA سه‌مقداری خود مرز و هر دو همسایه را. سپس Oracle، نوع/واحد/گام، شمول مرز و اثر جانبی را ثبت کنید. ورودی‌های دور، نوع نامعتبر، خرابی وابستگی و منابع محدود، هدف Robustness گسترده‌تری هستند. حدس خطا نیز باید از تاریخچه نقص، رفتار گذشته و فرضیه روشن بیاید، نه از فهرست تصادفی.

تحلیل مقدار مرزی (BVA) چیست؟

BVA یک تکنیک جعبه‌سیاه و مبتنی بر مشخصات است که مرزهای پارتیشن‌های هم‌ارزِ مرتب را تمرین می‌دهد. مرز جایی است که رفتار موردانتظار سیستم تغییر می‌کند: معتبر به نامعتبر، یک نرخ کارمزد به نرخ دیگر، باز به بسته یا یک وضعیت به وضعیت دیگر.

سرفصل رسمی ISTQB CTFL ۴.۰.۱ توضیح می‌دهد که BVA فقط برای پارتیشن‌های مرتب قابل استفاده است و معمولاً نقص‌هایی را می‌یابد که مرز در پیاده‌سازی یک واحد بالا/پایین افتاده یا اصلاً حذف شده است. برای جایگاه این تکنیک میان روش‌های Black Box، ابتدا راهنمای تست جعبه سیاه را ببینید.

چرا BVA بدون Equivalence Partitioning ناقص است؟

مرز متعلق به یک Partition است؛ پس قبل از انتخاب عدد باید داده را بر اساس رفتار موردانتظار تقسیم کنیم. برای شرط فرضی «مبلغ انتقال از ۱۰۰٬۰۰۰ تا ۵۰٬۰۰۰٬۰۰۰ ریال و شامل دو انتها»، سه پارتیشن داریم:

  • P1 نامعتبر: مبلغ کمتر از ۱۰۰٬۰۰۰؛
  • P2 معتبر: مبلغ از ۱۰۰٬۰۰۰ تا ۵۰٬۰۰۰٬۰۰۰؛
  • P3 نامعتبر: مبلغ بیشتر از ۵۰٬۰۰۰٬۰۰۰.

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

واژگان ضروری

اصطلاح معنا پرسش طراحی
Partition مجموعه‌ای که انتظار داریم سیستم اعضایش را یکسان پردازش کند رفتار دقیق این بخش چیست؟
Boundary کمینه یا بیشینه Partition مرتب؛ محل تغییر رفتار مرز متعلق به کدام بخش و شامل است یا نه؟
Neighbor نزدیک‌ترین مقدار قابل نمایش قبل یا بعد از مرز گام داده یک ریال، یک ثانیه یا چیز دیگری است؟
Coverage item موردی که معیار پوشش الزام می‌کند دو‌مقداری یا سه‌مقداری انتخاب شده است؟
Oracle رفتار موردانتظار برای تشخیص Pass/Fail فقط status یا اثر جانبی و state هم سنجیده می‌شود؟
Test basis منبع قانون: نیازمندی، قرارداد API، کد قانونی، تصمیم محصول اگر مرز تغییر کرد چه کسی و چه سندی مرجع است؟

BVA دو‌مقداری چیست؟

در BVA دو‌مقداری، برای هر مقدار مرزی دو Coverage Item داریم: خود مرز و نزدیک‌ترین همسایه آن در Partition مجاور. برای دامنه صحیح و شاملِ ۱۰۰٬۰۰۰ تا ۵۰٬۰۰۰٬۰۰۰ ریال، نقاط زیر کافی‌اند:

مرز مقدار تست Partition انتظار
کمینه ۹۹٬۹۹۹ P1 رد: زیر کمینه
کمینه ۱۰۰٬۰۰۰ P2 قبول، اگر قواعد دیگر برقرار باشند
بیشینه ۵۰٬۰۰۰٬۰۰۰ P2 قبول، اگر قواعد دیگر برقرار باشند
بیشینه ۵۰٬۰۰۰٬۰۰۱ P3 رد: بالاتر از سقف

پوشش دو‌مقداری چگونه حساب می‌شود؟

در این مثال چهار Coverage Item داریم. اگر هر چهار مقدار اجرا شود، پوشش BVA دو‌مقداری ۱۰۰٪ است. یک مقدار اسمی مثل ۱٬۰۰۰٬۰۰۰ ریال برای پوشش EP مفید است، اما بخشی از معیار BVA دو‌مقداری نیست. درصد پوشش فقط می‌گوید نقاط مدل‌شده اجرا شده‌اند؛ کامل‌بودن خود مدل یا درست‌بودن Oracle را تضمین نمی‌کند.

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

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

BVA سه‌مقداری چیست؟

در BVA سه‌مقداری، برای هر مرز خود مرز و هر دو همسایه را اجرا می‌کنیم. این روش سخت‌گیرانه‌تر است و نقصی مانند تبدیل اشتباه x <= 10 به x === 10 را می‌تواند آشکار کند؛ نقصی که مقدار ۱۰ و ۱۱ به‌تنهایی نمی‌بینند، اما ۹ آن را نشان می‌دهد.

مرز سه مقدار رفتار موردانتظار
کمینه ۱۰۰٬۰۰۰ ۹۹٬۹۹۹ / ۱۰۰٬۰۰۰ / ۱۰۰٬۰۰۱ رد / قبول / قبول
بیشینه ۵۰٬۰۰۰٬۰۰۰ ۴۹٬۹۹۹٬۹۹۹ / ۵۰٬۰۰۰٬۰۰۰ / ۵۰٬۰۰۰٬۰۰۱ قبول / قبول / رد

پوشش سه‌مقداری چگونه حساب می‌شود؟

در مثال حاضر شش Coverage Item داریم. اجرای هر شش مورد، پوشش سه‌مقداری ۱۰۰٪ می‌دهد. اگر دامنه کوچک یا مرزها نزدیک باشند، یک مقدار خام ممکن است در چند نقش ظاهر شود. تست را فقط با عدد Deduplicate نکنید؛ هویت «قانون، مرز و سمت مرز» را نیز در Test Case نگه دارید.

چه زمانی سه‌مقداری را انتخاب کنیم؟

  • مرز مالی، مجوز، سهمیه، ایمنی یا تعهد قانونی دارد؛
  • پیاده‌سازی شامل چند شرط، تبدیل نوع یا گردکردن است؛
  • تاریخچه نقص‌های Off-by-one یا اشتباه </<= داریم؛
  • داخل Partition بلافاصله کنار مرز رفتار متفاوتی پیدا می‌کند؛
  • هزینه اجرای شش مورد در برابر ریسک ناچیز است.

آیا Min±۱، مقدار اسمی و Max±۱ فرمول استاندارد BVA است؟

خیر؛ این مجموعه هفت‌تایی یک Heuristic رایج است، نه تعریف واحد و جهانی BVA. اضافه‌کردن مقدار اسمی می‌تواند پوشش EP را تکمیل کند و نقاط بیرونی برای Negative Test مفیدند، اما باید نام معیار را درست بنویسیم. امروز برای گزارش قابل ممیزی بهتر است صریح بگوییم:

  • EP: حداقل یک نماینده از هر Partition؛
  • ۲-value BVA: مرز و همسایه Partition مجاور؛
  • ۳-value BVA: مرز و هر دو همسایه؛
  • Robustness/Negative/Error Guessing: موارد اضافی با هدف و Oracle جدا.

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

پیش از ساخت تست، Boundary Contract بنویسید

یک جدول کوتاه، اختلاف برداشت محصول، Backend، Frontend و QA را زود آشکار می‌کند:

فیلد نمونه چرا مهم است؟
Rule ID و منبع WALLET-AMOUNT-۰۱ / سند محصول v3 ردیابی و مدیریت تغییر
نوع و نمایش Integer، ریال، JSON number جلوگیری از String/Float مبهم
Partitionها <۱۰۰۰۰۰، ۱۰۰۰۰۰..۵۰۰۰۰۰۰۰، >۵۰۰۰۰۰۰۰ پایه EP و BVA
شمول هر دو انتها شامل تعیین Oracle دقیق روی مرز
Resolution/Step یک ریال تعریف همسایه واقعی
Normalization API فقط رقم ASCII؛ UI رقم فارسی را تبدیل می‌کند جداکردن نمایش از قرارداد
Oracle معتبر ۲۰۱، یک Ledger Entry، مانده درست فراتر از status
Oracle نامعتبر ۴۲۲ + کد خطا، بدون Mutation اتمیک‌بودن و امنیت
وابستگی‌ها سقف روزانه، مانده و سطح احراز مرز چندمتغیره
مالک و تاریخ بازبینی Product + Backend / پایان فصل پیشگیری از مرز منقضی

نوشتن مثال‌های مرزی پیش از کد، بخشی از Testability است و با رویکرد Shift-left از Discovery تا CI هم‌راستا است.

مرز گسسته، پیوسته و Resolution

داده گسسته

برای عدد صحیح ریالی، همسایه ۱۰۰٬۰۰۰ به‌وضوح ۹۹٬۹۹۹ و ۱۰۰٬۰۰۱ است. برای تعداد آیتم، روز تقویمی یا Retry Count نیز Step طبیعی وجود دارد. اما باید نوع واقعی را ثبت کنید؛ «عدد» به‌تنهایی نمی‌گوید مقدار صحیح است یا اعشاری.

داده ظاهراً پیوسته

در عدد اعشاری، زمان و مختصات، همسایه ریاضی یکتایی نداریم. Resolution قرارداد را تعیین کنید: یک صدم، یک میلی‌ثانیه یا کوچک‌ترین واحد ذخیره‌شده. برای شرط rate < 2.50 با Resolution برابر ۰٫۰۱، نقاط ۲٫۴۹، ۲٫۵۰ و ۲٫۵۱ معنادارند. اگر سیستم مقدار را Round می‌کند، مرز قبل و بعد از Round دو Test Basis جدا می‌خواهد.

پول را Float فرض نکنید

برای پول، واحد کوچک صحیح یا نوع Decimal دقیق معمولاً Oracle روشن‌تری می‌دهد. قرارداد باید واحد، مقیاس اعشار، روش گردکردن و مرحله اعمال کارمزد را مشخص کند. «۵۰ میلیون» بدون ذکر ریال/تومان، نوع و شمول مرز، Test Case قابل تکرار نیست.

مرز شامل و غیرشامل

چهار عبارت زیر یکسان نیستند: min <= x <= max، min < x < max، بازه نیمه‌باز از چپ یا از راست. در JSON Schema، minimum/maximum و exclusiveMinimum/exclusiveMaximum این تفاوت را ماشین‌خوان می‌کنند.

قانون نقطه روی مرز همسایه مهم
x >= 100 ۱۰۰ معتبر ۹۹ نامعتبر، ۱۰۱ معتبر
x > 100 ۱۰۰ نامعتبر ۱۰۱ اولین مقدار صحیح معتبر
x <= 500 ۵۰۰ معتبر ۴۹۹ معتبر، ۵۰۱ نامعتبر
x < 500 ۵۰۰ نامعتبر ۴۹۹ آخرین مقدار صحیح معتبر

نسخه Draft قرارداد Schema را نیز Pin کنید؛ معنی بعضی Keywordها میان Draftهای قدیمی تغییر کرده است. Schema فقط ساختار و بخشی از Constraint را پوشش می‌دهد؛ قانون کسب‌وکار چندفیلدی همچنان Oracle جدا می‌خواهد.

Robust BVA با Robustness Testing یکی نیست

مفهوم هدف نمونه چیزی که تضمین نمی‌کند
BVA دو/سه‌مقداری مرز Partition مرتب ۹۹٬۹۹۹ / ۱۰۰٬۰۰۰ / ۱۰۰٬۰۰۱ خرابی dependency یا فشار منبع
Robust BVA در برخی منابع افزودن سمت نامعتبرِ نزدیک مرز Min−۱ و Max+۱ همه ورودی‌های بد یا کیفیت کلی Robustness
Negative Testing رفتار با ورودی/شرایط نامعتبر نوع String، فیلد غایب، State ممنوع امنیت کامل
Robustness Testing حفظ رفتار کنترل‌شده در شرایط نامطلوب Timeout، پاسخ خراب dependency، کمبود منبع، ورودی بزرگ Recovery یا Security مگر صریحاً سنجیده شود
Security Testing ریسک محرمانگی، صحت و دسترس‌پذیری در برابر تهدید Injection، Bypass، Resource abuse با چند ورودی نامعتبر کامل نمی‌شود

اصطلاح Robust BVA در کتاب‌ها یکدست نیست؛ بعضی منابع BVA «عادی» را فقط سمت معتبر و نسخه Robust را با همسایه نامعتبر تعریف می‌کنند. در مقابل، ISTQB ۴.۰.۱ در مدل دو/سه‌مقداری به Partition مجاور نگاه می‌کند. برای جلوگیری از اختلاف، نام نسخه و نقاط واقعی را بنویسید، نه فقط برچسب «Robust».

Robustness یک هدف کیفیتی گسترده‌تر است

Robustness باید به رفتار قابل مشاهده زیر Fault، افت دسترس‌پذیری و نیاز به Recovery تبدیل شود. سرفصل ISTQB Technical Test Analyst نمونه‌هایی مانند محدودکردن Memory/Bandwidth یا بررسی Recovery پس از Stress را شکل‌هایی از آزمون Robustness می‌داند. این‌ها با «Min−۱۰۰» پوشش داده نمی‌شوند.

Oracle تست استحکام چیست؟

  • Crash، Hang یا مصرف نامحدود منبع رخ ندهد؛
  • پاسخ خطا پایدار، بدون داده حساس و قابل ردیابی باشد؛
  • هیچ Mutation ناقص، Double charge یا Ledger ناسازگار ایجاد نشود؛
  • Timeout، Retry و Circuit Breaker طبق قرارداد عمل کنند؛
  • پس از رفع Fault، سیستم به وضعیت تعریف‌شده برگردد؛
  • Metric و Log برای تشخیص علت وجود داشته باشد.

حدس خطا (Error Guessing) چیست؟

Error Guessing یک تکنیک تجربه‌محور مکمل BVA است. طبق ISTQB، پیش‌بینی خطا، نقص و Failure بر دانش تستر از رفتار گذشته برنامه، خطاهای رایج توسعه‌دهندگان و Failureهای سامانه‌های مشابه تکیه دارد. «احساس می‌کنم این عدد بد است» هنوز یک Test Design قابل انتقال نیست.

منابع خوب برای حدس خطا

  • Defectهای Production و Postmortemهای قبلی؛
  • تفاوت Client و Server در Parse، Validation و Normalization؛
  • Diff کد و خطاهای اپراتوری رایج تیم؛
  • Telemetry: Error Code پرتکرار، Retry، Timeout و مقدارهای Outlier؛
  • سامانه‌های مشابه، استانداردها و Catalogهای ضعف؛
  • دانش دامنه محصول، پشتیبانی مشتری و عملیات.

قالب فرضیه Error Guessing

فیلد نمونه
فرضیه Backend رقم فارسی را String می‌گیرد اما Validator عدد ASCII می‌خواهد
شاهد سه Ticket پشتیبانی و یک Defect نسخه قبل
محرک "۱۰۰۰۰۰" در API خام و UI
Oracle UI تبدیل و قبول؛ API خام ۴۲۲ با کد پایدار؛ بدون Mutation
ریسک رد تراکنش معتبر یا اختلاف Client/Server
مالک/انقضا تیم Wallet؛ بازبینی پس از یکسان‌سازی Contract

Fault Attack می‌تواند این فهرست را نظام‌مند کند. هر مورد مفید که یک Failure واقعی پیدا کرد، به Regression Test و Test Basis برگردد؛ موارد کم‌ارزش نیز حذف یا بازنویسی شوند تا Checklist بی‌نهایت رشد نکند.

مثال کامل: مرزهای انتقال کیف پول ریالی

این اعداد کاملاً فرضی و آموزشی‌اند و نماینده محدودیت بانکی یا قانونی ایران نیستند. قرارداد یک API انتقال را چنین فرض می‌کنیم:

  1. amount عدد صحیح ریالی و شامل بازه ۱۰۰٬۰۰۰ تا ۵۰٬۰۰۰٬۰۰۰ است.
  2. dailyUsed + amount نباید از ۱۰۰٬۰۰۰٬۰۰۰ بیشتر شود.
  3. balance باید حداقل برابر amount + fee باشد.
  4. fee برای این مثال ثابت و ۱۰٬۰۰۰ ریال است.
  5. هر ورودی نامعتبر با ۴۲۲ و Error Code مشخص رد می‌شود و هیچ Ledger Entry نمی‌سازد.

مرحله ۱: مرز هر قانون مستقل

قانون روی مرز یک واحد پایین یک واحد بالا
کمینه مبلغ ۱۰۰٬۰۰۰ قبول ۹۹٬۹۹۹ رد ۱۰۰٬۰۰۱ قبول
بیشینه مبلغ ۵۰٬۰۰۰٬۰۰۰ قبول ۴۹٬۹۹۹٬۹۹۹ قبول ۵۰٬۰۰۰٬۰۰۱ رد
سقف روزانه مجموع ۱۰۰٬۰۰۰٬۰۰۰ قبول ۹۹٬۹۹۹٬۹۹۹ قبول ۱۰۰٬۰۰۰٬۰۰۱ رد
موجودی لازم balance = amount + fee قبول یک ریال کمتر رد یک ریال بیشتر قبول

مرحله ۲: تعامل مرزها

تست مستقل هر فیلد، خطای ترکیب را نمی‌بیند. مثال‌ها:

  • dailyUsed=99_900_000 و amount=100_000: دقیقاً روی سقف، قبول؛
  • dailyUsed=99_900_001 و amount=100_000: یک ریال بالاتر، رد؛
  • amount=50_000_000 و balance=50_010_000: موجودی دقیق، قبول؛
  • همان مبلغ با موجودی ۵۰٬۰۰۹٬۹۹۹: یک ریال کسری، رد؛
  • مبلغ معتبر اما State حساب مسدود: رد به دلیل State، نه Range.

وقتی تعداد پارامترها زیاد شد، ضرب دکارتی همه مقادیر انفجاری است. Decision Table برای قواعد نتیجه‌محور و Combinatorial Testing برای Interaction مناسب‌ترند. NIST SP 800-142 روش t-way، هزینه و محدودیت‌های آن را توضیح می‌دهد. شدت ۲-way یا بالاتر را بر اساس ریسک انتخاب کنید؛ هیچ عددی تضمین کشف همه نقص‌ها نیست.

مرحله ۳: Oracle چندلایه

لایه قبول رد
HTTP/Contract ۲۰۱ و Schema معتبر ۴۲۲ و Error Code پایدار
Domain مبلغ/کارمزد/مانده دقیق دلیل رد مطابق اولین/همه قواعد توافق‌شده
State یک Debit و یک Credit مرتبط هیچ Mutation یا رزرو باقی‌مانده
Idempotency تکرار کلید، همان نتیجه و بدون انتقال دوم Conflict تعریف‌شده در Payload متناقض
Observability Run/Trace/Transaction ID قابل پیوند Log بدون Secret و Metric دلیل رد

برای جزئیات status، state، idempotency و Assertion به راهنمای تست API رجوع کنید. Contract نیز باید با Consumer واقعی سازگار بماند؛ راهنمای Pact و تست قراردادی مرز این مسئولیت را توضیح می‌دهد.

مثال اجرایی BVA با Node.js

کد زیر یک Validator آموزشی و Test Table سه‌مقداری/تعامل را با Test Runner داخلی Node اجرا می‌کند. در سامانه واقعی، تابع Production را Import کنید؛ منطق را داخل Test دوباره پیاده نکنید.

import test from 'node:test';
import assert from 'node:assert/strict';

const MIN_AMOUNT = 100_000;
const MAX_AMOUNT = 50_000_000;
const DAILY_LIMIT = 100_000_000;
const FEE = 10_000;

function validateTransfer({ amount, dailyUsed, balance }) {
  if (!Number.isSafeInteger(amount)) return 'AMOUNT_TYPE';
  if (amount < MIN_AMOUNT) return 'AMOUNT_BELOW_MIN';
  if (amount > MAX_AMOUNT) return 'AMOUNT_ABOVE_MAX';
  if (dailyUsed + amount > DAILY_LIMIT) return 'DAILY_LIMIT';
  if (balance < amount + FEE) return 'INSUFFICIENT_BALANCE';
  return 'OK';
}

const base = { dailyUsed: 0, balance: 100_000_000 };

const boundaryCases = [
  ['lower - 1', 99_999, 'AMOUNT_BELOW_MIN'],
  ['lower', 100_000, 'OK'],
  ['lower + 1', 100_001, 'OK'],
  ['upper - 1', 49_999_999, 'OK'],
  ['upper', 50_000_000, 'OK'],
  ['upper + 1', 50_000_001, 'AMOUNT_ABOVE_MAX'],
];

for (const [name, amount, expected] of boundaryCases) {
  test(name, () => {
    assert.equal(validateTransfer({ ...base, amount }), expected);
  });
}

test('daily total exactly at boundary is accepted', () => {
  assert.equal(
    validateTransfer({
      amount: 100_000,
      dailyUsed: 99_900_000,
      balance: 1_000_000,
    }),
    'OK'
  );
});

test('one rial over daily boundary is rejected', () => {
  assert.equal(
    validateTransfer({
      amount: 100_000,
      dailyUsed: 99_900_001,
      balance: 1_000_000,
    }),
    'DAILY_LIMIT'
  );
});

test('one rial below required balance is rejected', () => {
  assert.equal(
    validateTransfer({
      amount: 100_000,
      dailyUsed: 0,
      balance: 109_999,
    }),
    'INSUFFICIENT_BALANCE'
  );
});
node --test wallet-boundaries.test.js

این کد چه چیزی را ثابت نمی‌کند؟

  • Schema و Parse واقعی HTTP ممکن است رفتار دیگری داشته باشد؛
  • Database، Transaction، Idempotency و Race در Unit Test حاضر نیستند؛
  • ترتیب Validationها بخشی از Contract است و باید توافق شود؛
  • محدوده Number در runtime و Serialization باید جدا بررسی شود؛
  • قواعد فرضی نمونه، قانون واقعی کسب‌وکار نیستند.

پس همین Vectorها را در سطح API/Component نیز اجرا کنید و فقط تعداد تست را معیار اعتماد ندانید.

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

مرزهای ماشین‌خوان با JSON Schema

بخش Range را تا جای ممکن در Contract بنویسید تا Generator، Validator و Documentation یک منبع مشترک داشته باشند:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["amount"],
  "properties": {
    "amount": {
      "type": "integer",
      "minimum": 100000,
      "maximum": 50000000
    }
  },
  "additionalProperties": false
}

محدودیت Schema

این Schema نوع و Range یک فیلد را می‌سنجد، اما «مبلغ + مصرف روزانه» یا «موجودی ≥ مبلغ + کارمزد» را لزوماً بیان نمی‌کند. همچنین Client-side validation قابل دورزدن است. OWASP Input Validation Cheat Sheet تفکیک Validation نحوی/معنایی، Allowlist و الزام Server-side را شرح می‌دهد.

BVA فقط برای عدد نیست

طول رشته و Unicode

برای نام ۲ تا ۵۰ «کاراکتر»، ابتدا تعریف کاراکتر را روشن کنید: Byte، Unicode code point یا Grapheme Cluster قابل‌دیدن برای کاربر؟ Emoji ترکیبی یا حرف با علامت می‌تواند چند Code Point و یک Grapheme باشد. Unicode Text Segmentation مرز Grapheme را تعریف می‌کند و Unicode Normalization شکل‌های هم‌ارز را توضیح می‌دهد.

برای حد طول ۵۰ Grapheme، مقادیر ۴۹، ۵۰ و ۵۱ را با متن فارسی، فاصله مجازی، Emoji و شکل‌های Normalized/غیرNormalized بسنجید. Oracle فقط پذیرش نیست: ذخیره، بازیابی، جست‌وجو، نمایش RTL و برش متن نیز باید درست بماند.

تاریخ و زمان

  • دقیقاً لحظه شروع/پایان اعتبار و یک Tick دو طرف؛
  • نیمه‌شب با Timezone قرارداد، نه Timezone لپ‌تاپ تستر؛
  • انتهای ماه، سال کبیسه و تبدیل شمسی/میلادی؛
  • Timestamp برابر Expiry: معتبر یا منقضی؟
  • Clock Skew، Precision پایگاه داده و Truncation API.

Collection، فایل و Pagination

برای آرایه ۱ تا ۱۰۰ عضو: ۰/۱/۲ و ۹۹/۱۰۰/۱۰۱. برای Upload، اندازه اعلام‌شده Header را با Byte واقعی مقایسه کنید. برای Pagination، صفحه صفر/یک، آخرین صفحه کامل/ناقص، limit و offset + limit را در برابر سقف Storage بسنجید.

State و شمارنده

مرز Retry یا تعداد تلاش Login تنها عدد نیست؛ توالی State دارد. اگر پس از تلاش پنجم Account قفل می‌شود، ۴/۵/۶ تلاش، زمان Unlock، تلاش موفق میان شکست‌ها و اجرای هم‌زمان را با State Transition Testing ترکیب کنید.

مرزهای مهم در محصول فارسی و ایرانی

  • رقم: 123، ۱۲۳ و ١٢٣ سه نمایش‌اند؛ سیاست Parse و Normalization باید روشن باشد.
  • حروف: ی/ی و ک/ک ممکن است در جست‌وجو یا Unique Constraint رفتار متفاوت بسازند.
  • فاصله: Space، نیم‌فاصله و چند Space روی min/maxLength و Trim اثر دارند.
  • پول: ریال/تومان، Separator، علامت منفی، صفرهای ابتدا و Overflow را جدا کنید.
  • تاریخ: تقویم شمسی در UI و Timestamp استاندارد در API باید قرارداد تبدیل و Timezone داشته باشند.
  • شناسه: شماره موبایل با +98/09، صفر ابتدا و String بودن را آگاهانه مدل کنید.
  • RTL: ترکیب متن راست‌به‌چپ با عدد، کد پیگیری و علامت‌ها باید هم ذخیره و هم نمایش داده شود.

مرزهای فارسی را از Dataset واقعیِ ناشناس یا مشخصات دامنه استخراج کنید و سپس با راهنمای تولید داده تست به Fixtureهای نسخه‌دار تبدیل کنید.

BVA و امنیت ورودی؛ مرز مسئولیت

ورودی زیر/بالای Range می‌تواند نقص Validation و گاهی مسیر DoS، تخصیص منبع یا Overflow را آشکار کند. CWE-20 طول، نوع، Range، مقدار مشتق، Consistency و قواعد دامنه را از ابعاد Input Validation می‌داند. اما چند Test Case مرزی معادل Security Test نیست.

کنترل‌هایی که BVA جایگزینشان نمی‌شود

  • Query پارامتری و Encoding متناسب با Context؛
  • Authorization و کنترل Object-level access؛
  • Rate Limit، Quota و محدودیت مصرف منبع؛
  • Parser امن، محدودیت عمق/اندازه و Timeout؛
  • Threat Modeling، SAST/DAST و Review تخصصی؛
  • Validation مستقل Server حتی اگر UI محدودیت دارد.

عبارت‌هایی مانند ' OR 1=1 «مقدار مرزی» نیستند؛ آن‌ها Payload امنیتی با Threat و Oracle متفاوت‌اند. طبقه‌بندی درست کمک می‌کند Coverage BVA را با پوشش امنیت اشتباه نگیریم.

مرزهای چندمتغیره را چگونه کنترل کنیم؟

مرحله اول: One Boundary at a Time

یک Baseline معتبر برای همه ورودی‌های دیگر نگه دارید و فقط مرز یک پارامتر را تغییر دهید. این روش علت Failure را واضح می‌کند، اما Interaction را نمی‌پوشاند.

مرحله دوم: قواعد کسب‌وکار با Decision Table

Amount valid؟ Daily cap valid؟ Balance sufficient؟ Account active؟ هر ترکیب feasible به Action مشخص می‌رسد. ترکیب‌های ناممکن را با دلیل حذف و اولویت Error Code را تعیین کنید.

مرحله سوم: Pairwise یا t-way

برای هر عامل، Partition و مقادیر مرزی منتخب را وارد مدل کنید؛ Constraintهای ناممکن را ثبت کنید؛ سپس Strength متناسب ریسک را بسازید. Combinatorial Testing مکمل Oracle و Risk Analysis است، نه جایگزین آن. برای انتخاب عمق، راهنمای تست مبتنی بر ریسک کمک می‌کند.

هر تست نامعتبر یک علت اصلی

اگر Amount، Token و State هم‌زمان نامعتبر باشند، نمی‌دانید Validator دوم اجرا شده یا نقص اول آن را پوشانده است. برای Negative Boundary، یک invalidity اصلی در هر Test نگه دارید؛ سپس تست‌های Interaction را جدا و با Oracle ترتیب خطا بسازید.

راهبرد اتوماسیون BVA

۱. نقاط قطعی را اول ثابت کنید

مرزهای قرارداد باید Test Caseهای نام‌دار و Deterministic باشند. نام تست Rule و سمت مرز را نشان دهد؛ داده و Expected Result در Review قابل دیدن باشند. Random Generation نباید تنها شانس برخورد با مرز باشد.

۲. از Schema، Candidate بسازید

Generator می‌تواند برای هر minimum/maximum/minLength/maxLength نقاط دو/سه‌مقداری بسازد. با این حال، واحد، Resolution، شمول، Normalization و قانون چندفیلدی را از Metadata دامنه اضافه کنید.

۳. Property-Based Testing را مکمل کنید

مستندات Strategyهای Hypothesis برای Integer، Decimal، Float، Date، Text و Shrinking گزینه‌های Range دارد. Property می‌تواند بگوید «هر مبلغ معتبر یا OK است یا فقط به دلیل قاعده دیگری رد می‌شود» و Failure را به نمونه کوچک‌تر Shrink کند. اما مثال‌های مرزی مهم را همچنان Explicit نگه دارید تا Coverage قرارداد تصادفی نباشد.

۴. Vector را در لایه مناسب تکرار کنید

  • Unit: همه نقاط و Oracle سریع منطق؛
  • Component/API: Parse، Schema، Database و Contract؛
  • UI: تعداد کم برای input mode، پیام، رقم فارسی و Accessibility؛
  • Integration: مرزهای dependency و Idempotency؛
  • Production-safe monitoring: Metric ردها، بدون تزریق بار مخرب.

BVA در CI/CD و مدیریت تغییر

Lane هر Commit

تست‌های دو‌مقداری و مرزهای بحرانی سه‌مقداری در Unit/Component سریع اجرا شوند. Schema Diff باید تغییر Minimum/Maximum، شمول یا Length را به Review محصول و QA بفرستد.

Lane زمان‌بندی‌شده

Property-based گسترده، ورودی‌های بسیار بزرگ، ترکیب‌های t-way، Unicode وسیع و Fault Injection در محیط کنترل‌شده اجرا شوند. Seed، نسخه Generator و Counterexample در Artifact ذخیره شود.

تغییر امن مرز

  1. مصرف‌کنندگان و داده موجود را اندازه بگیرید.
  2. Contract جدید و دوره سازگاری را تعریف کنید.
  3. Provider ابتدا دامنه جدید را بدون شکستن قدیمی بپذیرد.
  4. Consumerها و داده Migration شوند.
  5. Telemetry استفاده از مرز قدیمی را تأیید کند.
  6. قانون منسوخ حذف و Test Caseها/Schema هم‌زمان به‌روز شوند.

تغییر Range یک تغییر رفتار است، نه صرفاً ویرایش Test Data. Contract Test و مشاهده Consumerها از انتشار ناسازگار جلوگیری می‌کند.

پوشش و معیار سالم

مخرج را نسخه‌دار کنید

برای هر Rule، Coverage Itemهای دو/سه‌مقداری را در Catalog نگه دارید. فرمول ساده است: تعداد Items اجراشده تقسیم بر Items شناسایی‌شده. اما تغییر Partition باید مخرج و نسخه را عوض کند؛ مقایسه درصد قبل و بعد بدون این Context فریبنده است.

معیارهای مفید

  • درصد قواعد پرریسک با Boundary Contract و مالک؛
  • پوشش ۲/۳-value به تفکیک Rule و لایه؛
  • نقص مرزی Escaped بر حسب نوع: Inclusivity، Parse، Interaction، Unit؛
  • زمان از تغییر Contract تا به‌روزرسانی Test؛
  • نسبت Error Guessهای تبدیل‌شده به Regression معتبر؛
  • Flaky/Infra Error جدا از Product Failure.

معیارهای قابل بازی

  • تعداد خام Test Case؛
  • ادعای «۱۰۰٪ BVA» بدون نسخه و مخرج؛
  • تعداد ورودی نامعتبر تولیدشده؛
  • درصد Automation بدون Oracle و Risk؛
  • تعداد Bug بدون Severity، Root Cause و فرصت کشف.

خطاهای رایج در تحلیل مقدار مرزی

  • شروع از عدد، نه Partition: نقاط زیبا داریم اما رفتارها مدل نشده‌اند.
  • نام‌گذاری هر Negative Test به‌عنوان BVA: نوع نامعتبر و Injection مرز عددی نیستند.
  • ابهام واحد: ریال/تومان، Byte/Character، ثانیه/میلی‌ثانیه مشخص نیست.
  • فرض Step=۱: در Decimal، Timestamp و Bucket این فرض می‌تواند غلط باشد.
  • فقط سمت معتبر: اشتباه در Reject کردن همسایه نامعتبر دیده نمی‌شود.
  • فقط status: Mutation ناقص، پیام غلط یا Ledger ناسازگار پنهان می‌ماند.
  • همه مرزها در یک تست: Defect Masking تشخیص علت را سخت می‌کند.
  • ضرب دکارتی کور: Suite بزرگ، کند و بدون اولویت می‌شود.
  • مرز ثابت در کد تست: Contract تغییر می‌کند اما Test سبزِ منقضی می‌ماند.
  • اعتماد به Validation سمت Client: API خام می‌تواند UI را دور بزند.
  • Unicode به‌عنوان ASCII: طول و برابری متن فارسی اشتباه محاسبه می‌شود.
  • تبدیل Error Guessing به خرافه: فرضیه بدون شاهد، مالک و بازبینی می‌ماند.

فرآیند گام‌به‌گام طراحی تست مرزی

  1. Test Basis، Rule ID، نوع، واحد و رفتار را جمع کنید.
  2. Partitionهای مرتبِ معتبر و نامعتبر را بدون هم‌پوشانی تعریف کنید.
  3. کمینه/بیشینه، شمول و Resolution هر Partition را ثبت کنید.
  4. بر اساس ریسک، ۲-value یا ۳-value را انتخاب کنید.
  5. برای هر Item، Precondition، Input و Oracle چندلایه بسازید.
  6. یک invalidity اصلی در هر Negative Test نگه دارید.
  7. تعامل مرزها را با Decision Table و t-way اضافه کنید.
  8. Robustness و Security را با هدف‌های جدا گسترش دهید.
  9. Error Guessها را از Defect/Telemetry به فرضیه قابل آزمون تبدیل کنید.
  10. Vectorها را در Unit/API/UI/Integration به اندازه Risk توزیع کنید.
  11. Coverage، نسخه Contract، Artifact و Failure class را در CI ثبت کنید.
  12. پس از هر تغییر Rule یا Escaped Defect، Catalog را بازبینی کنید.

چک‌لیست آماده بازبینی

  • Partitionها مرتب، غیرتهی و بدون هم‌پوشانی‌اند.
  • نوع، واحد، Precision/Resolution و Normalization ثبت شده است.
  • مرز شامل/غیرشامل و اولین/آخرین مقدار معتبر روشن است.
  • نسخه ۲-value یا ۳-value و Coverage Itemها نام‌گذاری شده‌اند.
  • Oracle شامل پاسخ، State، اثر جانبی و Observability است.
  • مقادیر نامعتبر یک علت اصلی دارند و Defect Masking کم شده است.
  • تعامل فیلدها با Decision Table/t-way پوشش دارد.
  • Robustness، Recovery و Security با BVA یکی فرض نشده‌اند.
  • Error Guess شاهد، فرضیه، مالک، انقضا و نتیجه دارد.
  • مرز فارسی: رقم، Unicode، پول، تاریخ و RTL بررسی شده است.
  • Client و Server هر دو تست شده‌اند، اما Server مرجع امنیت است.
  • Schema، Test و Contract در تغییر مرز هم‌زمان Review می‌شوند.

جمع‌بندی

تحلیل مقدار مرزی پیشرفته به معنی افزودن چند ورودی عجیب نیست؛ یعنی مرز را به‌عنوان یک قرارداد قابل‌ردیابی مدل کنیم. BVA دو‌مقداری برای هر مرز خود مرز و همسایه Partition مجاور را می‌سنجد؛ BVA سه‌مقداری هر دو سمت را می‌افزاید. Robust BVA فقط یک اصطلاح محدود در بعضی منابع است، در حالی که Robustness Testing رفتار سیستم زیر ورودی، Fault یا منبع نامطلوب را با Oracle گسترده می‌سنجد. Error Guessing نیز زمانی ارزشمند است که تجربه را به فرضیه و Regression قابل بازبینی تبدیل کند.

مسیر قابل دفاع چنین است: Test Basis → Partition → Boundary/Resolution → ۲ یا ۳-value → Oracle → Interaction → Robustness/Error Guess → Automation/Evidence. اگر فقط اعداد را فهرست کنیم، ممکن است Test Case زیاد داشته باشیم اما دقیقاً همان مرز خطرناک کسب‌وکار را نبینیم.

سوالات متداول

تفاوت Equivalence Partitioning و BVA چیست؟

EP داده را به Partitionهایی تقسیم می‌کند که انتظار رفتار یکسان دارند و از هر بخش نماینده می‌گیرد. BVA فقط روی مرز Partitionهای مرتب و همسایه‌هایشان تمرکز می‌کند. ابتدا EP، سپس BVA؛ هیچ‌کدام به‌تنهایی تعامل همه ورودی‌ها را پوشش نمی‌دهند.

BVA دو‌مقداری بهتر است یا سه‌مقداری؟

سه‌مقداری سخت‌گیرانه‌تر است چون خود مرز و هر دو همسایه را می‌سنجد؛ دو‌مقداری هزینه کمتری دارد. برای مرز مالی، مجوز، سهمیه یا سابقه Off-by-one معمولاً سه‌مقداری مناسب‌تر است. انتخاب باید بر Risk و هزینه اجرا تکیه کند.

فرق Robust BVA و Robustness Testing چیست؟

Robust BVA در برخی منابع یعنی افزودن مقادیر درست بیرون مرز معتبر. Robustness Testing هدف گسترده‌تری دارد و می‌تواند نوع نامعتبر، ورودی بسیار بزرگ، Timeout، خرابی dependency، کمبود منبع و Recovery را بسنجد. نام نقاط و Oracle را صریح بنویسید چون اصطلاحات منابع یکدست نیستند.

آیا BVA برای رشته و تاریخ هم کاربرد دارد؟

بله، اگر Partition مرتب و Resolution معنادار داشته باشیم: طول ۴۹/۵۰/۵۱ Grapheme، لحظه قبل/روی/بعد Expiry، ۰/۱/۲ عضو یا ۹۹/۱۰۰/۱۰۱ رکورد. واحد اندازه‌گیری و Unicode/Timezone باید دقیق تعریف شوند.

چگونه BVA را بدون انفجار تعداد تست خودکار کنیم؟

نقاط قطعی دو/سه‌مقداری را Explicit نگه دارید، هر بار یک مرز را روی Baseline معتبر تغییر دهید، قواعد چندفیلدی را با Decision Table و t-way کاهش دهید و Property-based Testing را برای فضای بزرگ مکمل کنید. Suite را بر اساس ریسک میان Unit، API و Integration توزیع کنید.

منابع رسمی و قابل بازبینی

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