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

جمله «از هر کلاس هم‌ارز فقط یک مقدار را تست کنید» می‌تواند هم بسیار هوشمندانه باشد و هم خطرناک. اگر null، رشته خالی و فیلد غایب را یک کلاس فرض کنیم، اما Parser، Validator و منطق کسب‌وکار با هرکدام متفاوت رفتار کنند، نماینده انتخابی دو Failure مهم را پنهان می‌کند. بخش‌بندی هم‌ارزی یا Equivalence Partitioning هنر کم‌کردن تست نیست؛ هنر ساختن یک فرضیه قابل دفاع درباره رفتار داده‌هاست.

در این راهنمای پیشرفته، Partitionهای معتبر و نامعتبر، چند مجموعه Partition، پوشش Each Choice، ورودی JSON، Dependency و Constraint، روش انتخاب نماینده و ترکیب EP با Decision Table، Pairwise، BVA و Property-Based Testing را یاد می‌گیرید. مثال اصلی، یک Checkout فرضی در بازارگاه ایرانی است و کد آن واقعاً قابل اجراست.

پاسخ کوتاه: دامنه تست را بر اساس رفتار موردانتظار به مجموعه‌های غیرتهی و بدون هم‌پوشانی تقسیم کنید. برای پوشش EP، هر Partition شناسایی‌شده—معتبر و نامعتبر—باید حداقل یک‌بار تمرین شود. وقتی چند پارامتر دارید، Each Choice فقط می‌گوید هر Choice از هر عامل دیده شده؛ تعامل Choiceها را تضمین نمی‌کند. قواعد معلوم را با Decision Table، تعامل‌های ناشناخته را با Pairwise/t-way، مرزها را با BVA و توالی را با State Transition تکمیل کنید.

بخش‌بندی هم‌ارزی (EP) چیست؟

EP یک تکنیک جعبه‌سیاه و مبتنی بر مشخصات است. داده مرتبط با Test Object—ورودی، خروجی، تنظیمات، مقدار داخلی، زمان یا پارامتر Interface—به Partitionهایی تقسیم می‌شود که انتظار داریم اعضای هر Partition به شکل یکسان پردازش شوند. سپس دست‌کم یک نماینده از هر Partition را تست می‌کنیم.

ISTQB CTFL 4.0.1 تأکید می‌کند Partitionها می‌توانند پیوسته یا گسسته، مرتب یا نامرتب، محدود یا نامحدود باشند؛ اما باید غیرتهی و بدون هم‌پوشانی باشند. برای جایگاه EP میان تکنیک‌ها، راهنمای تست جعبه سیاه را ببینید.

هم‌ارزی یک فرضیه است، نه حقیقت

دو مقدار زمانی برای هدف تست حاضر هم‌ارزند که انتظار داریم خروجی، State، اثر جانبی و مسیر خطای مرتبط یکسانی بسازند. این ادعا مطلق نیست. ممکن است دو مقدار برای Validation هم‌ارز باشند، اما برای Performance یا Authorization نباشند. بنابراین روی هر Partition بنویسید: «نسبت به کدام رفتار و تحت چه Contextی؟»

معتبر و نامعتبر وابسته به قراردادند

Valid می‌تواند یعنی «باید پردازش شود» و Invalid یعنی «باید رد یا نادیده گرفته شود». اما بعضی تیم‌ها مقدار خارج از Specification را Undefined می‌دانند. رفتار موردانتظار را صریح کنید: Reject، Ignore، Default، Normalize، Quarantine یا Process. برچسب Invalid بدون Oracle ارزشی ندارد.

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

Coverage Itemهای EP همان Partitionهای شناسایی‌شده‌اند. فرمول:

EP Coverage = Partitionهای اجراشده ÷ کل Partitionهای نسخه مدل × ۱۰۰

مخرج را همراه نسخه Test Basis نگه دارید. اضافه‌شدن یک Partition جدید باید درصد تاریخی را با Context جدید نشان دهد، نه اینکه مدل قبلی را ظاهراً ناقص جلوه دهد. ۱۰۰٪ EP Coverage نیز کامل‌بودن Partitionها یا درست‌بودن Oracle را تضمین نمی‌کند.

آیا «بخش‌بندی هم‌ارزی پیشرفته» یک تکنیک رسمی جداست؟

معمولاً نه. در منابع استاندارد، همان EP برای داده ساده یا پیچیده اعمال می‌شود. واژه «پیشرفته» در این مقاله یک عنوان عملی برای دشواری‌های واقعی است:

  • چند معیار Partition برای یک فیلد یا Object؛
  • ورودی‌های ساختاریافته، Optional و Nullable؛
  • وابستگی میان فیلدها، State و هویت کاربر؛
  • Constraintهای feasible/infeasible؛
  • انتخاب Interaction Coverage بدون انفجار ترکیب؛
  • Oracle چندلایه و تغییرپذیری Contract.

اگر فقط سن/Range ساده دارید، راهنمای عملی EP و BVA نقطه شروع سبک‌تری است. این نوشته مالک مدل‌های چندبعدی و ساختاریافته است.

ویژگی‌های یک Partition خوب

۱. غیرتهی باشد

برای هر Partition حداقل یک مقدار feasible وجود داشته باشد. «کاربر مهمان با تخفیف مخصوص اعضا و نتیجه موفق» شاید به دلیل Rule محصول تهی باشد؛ آن را Test Case عادی نسازید.

۲. هم‌پوشان نباشد

هر مقدار نسبت به یک معیار باید دقیقاً در یک Partition قرار گیرد. اگر «کاربر فعال» و «کاربر ویژه» هم‌پوشانی دارند، معیارها را جدا کنید: account_status و customer_tier.

۳. دامنه در Scope را پوشش دهد

حالت Unknown را فراموش نکنید: مقدار Enum جدید، فیلد اضافه، Encoding متفاوت یا State مهاجرتی. Scope می‌تواند عمداً محدود باشد، اما حذف باید ثبت شود.

۴. رفتار میان Partitionها متمایز باشد

اگر دو بخش همیشه Oracle یکسان، Risk یکسان و منطق یکسان دارند، احتمال Over-partitioning وجود دارد. اگر دو عضو یک بخش رفتار متفاوت دارند، Under-partitioning رخ داده و باید بخش شکافته شود.

۵. به Test Basis و Oracle وصل باشد

هر Partition باید Rule ID، منبع، Owner و Expected Outcome داشته باشد. «ورودی عجیب» یا «Edge case» شناسه قابل نگهداری نیست.

مدل Category، Choice و Constraint

برای ورودی پیچیده، یک جدول مدل بسازید:

جزء تعریف نمونه
Factor/Category بعد مستقلی که رفتار را تغییر می‌دهد وضعیت حساب، نوع سبد، آدرس، کوپن
Choice/Partition بخش هم‌ارز در آن بعد کوپن غایب، فعال، منقضی، نامرتبط
Representative مقدار/Fixture منتخب کوپن SUMMER10 فعال
Constraint ترکیب ناممکن یا خارج Scope نوع کالای حذف‌شده‌ای که Fixture ندارد
Expected outcome Oracle رفتاری COUPON_EXPIRED و بدون رزرو موجودی
Risk/Source دلیل وجود و اولویت Incident، نیازمندی، Schema، Support Ticket

Constraint را با Negative Test اشتباه نگیرید

Constraint می‌گوید ترکیب در مدل غیرممکن یا عمداً خارج Scope است. اما اگر Client می‌تواند Payload «ناممکن» را بفرستد، آن ترکیب ممکن است دقیقاً یک Negative Test باشد. مثلاً UI اجازه کوپن منقضی نمی‌دهد، اما API خام می‌تواند؛ پس آن را حذف نکنید، بلکه Oracle ردشدن Server-side را بنویسید.

Don’t Care با Infeasible فرق دارد

وقتی حساب Blocked است، نوع کوپن شاید بر نتیجه ACCOUNT_BLOCKED اثری ندارد؛ این Don’t Care برای Rule فعلی است، نه اینکه ترکیب وجود ندارد. نگهداری این تفاوت برای Decision Table و تحلیل پوشش ضروری است.

نماینده هر Partition را چگونه انتخاب کنیم؟

نماینده معمولی

مقداری پایدار، خوانا و نزدیک رفتار روزمره انتخاب کنید. از شناسه Production یا داده شخصی استفاده نکنید. Fixture باید نسخه و Owner داشته باشد.

نماینده پرریسک

اگر یک Partition اعضای هم‌ارز زیادی دارد اما سابقه نقص در Unicode، مبلغ بزرگ یا Product خاص دیده‌اید، آن عضو را به نماینده دوم یا Error-Guess Regression تبدیل کنید؛ ادعای هم‌ارزی را بی‌دلیل نشکنید.

مرز را به BVA بسپارید

EP از هر Partition نماینده می‌گیرد؛ BVA مرز Partition مرتب را می‌سنجد. آموزش BVA دو و سه‌مقداری انتخاب دقیق Boundary و Neighbor را پوشش می‌دهد. یک مقدار مرزی می‌تواند هم EP و هم BVA را پوشش دهد، اما در گزارش دو معیار را جدا نگه دارید.

فرضیه را با جایگزینی بیازمایید

گاهی نماینده را با چند عضو دیگر همان Partition عوض کنید. اگر Outcome تغییر کرد، یا Partition بد تعریف شده یا Context پنهانی وجود دارد. Property-Based Testing می‌تواند این Invariance را در فضای بزرگ نمونه‌برداری و Counterexample کوچک تولید کند.

ورودی پیچیده را لایه‌به‌لایه Partition کنید

برای یک Request JSON، «Valid/Invalid JSON» بیش از حد درشت است. لایه‌ها را جدا کنید:

لایه Partitionهای نمونه Oracle
Transport Media type درست/غلط، Body خالی، JSON malformed ۴۰۰/۴۱۵، بدون ورود به Domain
Schema Required حاضر/غایب، type درست/غلط، extra field مجاز/نامجاز 422 + path/code
Semantic فرمت معتبر اما Rule معتبر/نامعتبر کد Domain مشخص
Reference شناسه موجود/حذف‌شده/ناموجود ۴۰۴/۴۰۹ طبق Contract
Authorization مالک/غیرمالک/Scope ناکافی ۴۰۳ بدون افشای داده
State Draft/Active/Blocked/Expired Transition یا Conflict درست
Interaction ترکیب فیلدها و قواعد Action جدول تصمیم
Operational Payload کوچک/بزرگ، عمق، نرخ Limit کنترل‌شده، نه Crash

راهنمای Object در JSON Schema تفاوت required، property غایب، null و additionalProperties را نشان می‌دهد. مهم: تعریف property در properties به‌تنهایی آن را Required نمی‌کند.

Missing، Null، Empty و Blank یک Partition نیستند

حالت نمونه JSON پرسش رفتاری
Missing {} Default، Required error یا حفظ مقدار قبلی؟
Null {"note": null} پاک‌کردن، Unknown یا type error؟
Empty string {"note": ""} مقدار معتبر تهی یا Validation error؟
Blank {"note": " "} Trim پیش از Validation یا ذخیره؟
Wrong type {"note": 42} Reject یا Coerce؟
Empty collection {"items": []} سبد خالی معتبر است؟

اگر همه این حالت‌ها طبق Contract یک Error Code، Status و بدون Mutation می‌سازند، می‌توانند برای هدف خاص هم‌ارز باشند. اگر Parser یا Business Logic فرق دارد، Partitionهای جدا لازم است.

Schema چگونه به Partitioning کمک می‌کند؟

Keywordها را به Choice تبدیل کنید

  • required: حاضر/غایب؛
  • type: نوع درست/هر نوع نامعتبر مرتبط؛
  • enum: عضو شناخته‌شده/ناشناخته؛
  • minimum/maximum: Partitionهای Range و سپس BVA؛
  • minLength/maxLength: طول معتبر/کم/زیاد؛
  • additionalProperties: فیلد اضافه مجاز/نامجاز؛
  • oneOf/anyOf/allOf: شاخه‌های ترکیب و ابهام چندتطبیقی؛
  • if/then/else و dependentRequired: Dependency.

Conditional Validation در JSON Schema وابستگی propertyها را ماشین‌خوان می‌کند. اما Ruleهایی مانند موجودی، مالکیت و اعتبار کوپن بیرون Schema باقی می‌مانند.

OpenAPI قرارداد سطح API را می‌دهد

OpenAPI Specification 3.2.0 سطح API، Parameter، Media Type، Schema و Response را توصیف می‌کند. نسخه OAS و Dialect Schema را Pin کنید؛ Toolingهای مختلف ممکن است Keyword یا format را یکسان اجرا نکنند. تست Contract باید رفتار Provider واقعی را بسنجد، نه فقط صحت فایل.

Each Choice Coverage برای چند مجموعه Partition

وقتی Test Object چند پارامتر دارد، یک Test Case هم‌زمان یک Choice از چند Factor را پوشش می‌دهد. ساده‌ترین معیار ISTQB، Each Choice است: هر Partition از هر مجموعه دست‌کم یک‌بار اجرا شود.

فرض کنید Checkout پنج Factor با تعداد Choiceهای ۳، ۳، ۴، ۴ و ۲ دارد. تعداد Coverage Itemهای Each Choice برابر مجموع آن‌ها، یعنی ۱۶ است؛ نه حاصل‌ضرب ۲۸۸. با چند Test Case می‌توان همه ۱۶ مورد را پوشش داد، اما این معیار درباره پوشش Pairها یا Ruleهای ترکیبی ادعایی ندارد.

مخرج Each Choice

مخرج، مجموع Partitionهای همه Factorها در نسخه مدل است. هر Test Case می‌تواند چند Item را پوشش دهد. Constraint، Choice تهی و Out-of-scope باید پیش از محاسبه روشن شوند. Report باید Uncovered Item را با Factor/Choice نشان دهد، نه فقط درصد کل.

محدودیت Each Choice

ممکن است cart=mixed و coupon=expired هر دو جداگانه پوشش داشته باشند اما هیچ‌گاه با هم اجرا نشده باشند. اگر Failure فقط در ترکیب آن‌ها رخ دهد، Each Choice سبز می‌ماند. اینجا معیار Interaction لازم است.

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

مسئله تکنیک مکمل Coverage Item
لبه Partition مرتب BVA دو/سه‌مقداری Boundary و Neighbor
Ruleهای معلوم چندشرطی Decision Table ستون feasible/Rule
Interaction ناشناخته میان Factorها Pairwise/t-way ترکیب‌های feasible با Strength انتخابی
رفتار وابسته به توالی و State State Transition State/Transition/Sequence
ساختار و مسیر داخلی Branch/Path Testing Branch یا Path مستقل
ابهام و یادگیری Exploratory Testing Charter/Coverage outline/یافته

برای Ruleهای معلوم، آموزش Decision Table و برای Interaction، راهنمای Pairwise و Constraint را ببینید. NIST SP 800-142 t-way، Constraint، Oracle و Trade-offهای Combinatorial Testing را توضیح می‌دهد.

مثال کامل: Partition Model برای Checkout بازارگاه

قواعد زیر کاملاً فرضی‌اند:

  1. حساب Blocked اجازه Checkout ندارد.
  2. سبد Physical یا Mixed به آدرس معتبر نیاز دارد؛ Digital می‌تواند بدون آدرس باشد.
  3. موجودی ناکافی Checkout را رد می‌کند.
  4. کوپن Expired یا Not-applicable با Error Code جدا رد می‌شود.
  5. نتیجه نامعتبر هیچ رزرو موجودی یا پرداخت ایجاد نمی‌کند.

Factorها و Choiceها

Factor Choiceها نماینده/معنا
User guest / member / blocked هویت و State حساب
Cart digital / physical / mixed نیاز به Delivery
Address absent / valid_tehran / valid_other / invalid Presence، Region و Semantic validity
Coupon none / active / expired / not_applicable حالت Rule تخفیف
Stock sufficient / insufficient State موجودی

Oracle و ترتیب Ruleها

ترتیب خطا بخشی از Contract است: ابتدا Account، سپس Address، Stock و Coupon. اگر محصول می‌خواهد تمام خطاها را برگرداند، Partition و Oracle عوض می‌شود. هر Error باید status، code، path و «بدون Mutation» را بسنجد.

یک Suite نمونه با Each Choice کامل

# User Cart Address Coupon Stock Outcome
1 guest digital absent none sufficient OK
2 member physical valid_tehran active sufficient OK
3 blocked mixed valid_other none sufficient ACCOUNT_BLOCKED
4 guest physical invalid none sufficient ADDRESS_INVALID
5 member mixed valid_other expired sufficient COUPON_EXPIRED
6 guest digital absent not_applicable sufficient COUPON_NOT_APPLICABLE
7 member physical valid_other none insufficient OUT_OF_STOCK
8 guest physical absent active sufficient ADDRESS_REQUIRED

این هشت Case همه ۱۶ Choice را دست‌کم یک‌بار پوشش می‌دهند. اما Pairwise کامل نیستند؛ مثلاً هر Pair ممکن میان User و Coupon یا Cart و Stock تضمین نشده است. ادعای Coverage را دقیقاً Each Choice بنویسید.

کد اجرایی: Outcome و پوشش Partitionها

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

const model = {
  user: ['guest', 'member', 'blocked'],
  cart: ['digital', 'physical', 'mixed'],
  address: ['absent', 'valid_tehran', 'valid_other', 'invalid'],
  coupon: ['none', 'active', 'expired', 'not_applicable'],
  stock: ['sufficient', 'insufficient'],
};

function classifyCheckout(input) {
  if (input.user === 'blocked') return 'ACCOUNT_BLOCKED';
  if (input.cart !== 'digital' && input.address === 'absent') {
    return 'ADDRESS_REQUIRED';
  }
  if (input.address === 'invalid') return 'ADDRESS_INVALID';
  if (input.stock === 'insufficient') return 'OUT_OF_STOCK';
  if (input.coupon === 'expired') return 'COUPON_EXPIRED';
  if (input.coupon === 'not_applicable') {
    return 'COUPON_NOT_APPLICABLE';
  }
  return 'OK';
}

const cases = [
  {
    name: 'digital guest',
    user: 'guest',
    cart: 'digital',
    address: 'absent',
    coupon: 'none',
    stock: 'sufficient',
    expected: 'OK',
  },
  {
    name: 'physical member in Tehran',
    user: 'member',
    cart: 'physical',
    address: 'valid_tehran',
    coupon: 'active',
    stock: 'sufficient',
    expected: 'OK',
  },
  {
    name: 'blocked mixed cart',
    user: 'blocked',
    cart: 'mixed',
    address: 'valid_other',
    coupon: 'none',
    stock: 'sufficient',
    expected: 'ACCOUNT_BLOCKED',
  },
  {
    name: 'invalid address',
    user: 'guest',
    cart: 'physical',
    address: 'invalid',
    coupon: 'none',
    stock: 'sufficient',
    expected: 'ADDRESS_INVALID',
  },
  {
    name: 'expired coupon',
    user: 'member',
    cart: 'mixed',
    address: 'valid_other',
    coupon: 'expired',
    stock: 'sufficient',
    expected: 'COUPON_EXPIRED',
  },
  {
    name: 'not applicable coupon',
    user: 'guest',
    cart: 'digital',
    address: 'absent',
    coupon: 'not_applicable',
    stock: 'sufficient',
    expected: 'COUPON_NOT_APPLICABLE',
  },
  {
    name: 'out of stock',
    user: 'member',
    cart: 'physical',
    address: 'valid_other',
    coupon: 'none',
    stock: 'insufficient',
    expected: 'OUT_OF_STOCK',
  },
  {
    name: 'address required',
    user: 'guest',
    cart: 'physical',
    address: 'absent',
    coupon: 'active',
    stock: 'sufficient',
    expected: 'ADDRESS_REQUIRED',
  },
];

for (const item of cases) {
  test(item.name, () => {
    assert.equal(classifyCheckout(item), item.expected);
  });
}

test('every declared partition has a representative', () => {
  for (const [factor, choices] of Object.entries(model)) {
    for (const choice of choices) {
      assert.ok(
        cases.some((item) => item[factor] === choice),
        'uncovered: ' + factor + '=' + choice
      );
    }
  }
});
node --test checkout-partitions.test.js

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

  • Outcome هشت Case با Rule آموزشی هماهنگ است؛
  • هر Choice اعلام‌شده در Model حداقل یک نماینده دارد؛
  • اگر Choice جدید اضافه و Case فراموش شود، Test پوشش Fail می‌شود.

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

  • Pairwise یا همه ترکیب‌ها پوشش داده نشده‌اند؛
  • مدل همه Partitionهای واقعی را پیدا نکرده است؛
  • HTTP، Database، Race، Authorization و Mutation واقعی تست نشده‌اند؛
  • ترتیب Ruleها با نیازمندی واقعی یکی نیست مگر Stakeholder تأیید کند؛
  • یک Representative رفتار تمام اعضای Partition را اثبات نمی‌کند.

Decision Table یا Pairwise؛ کدام را انتخاب کنیم؟

وقتی Rule معلوم است: Decision Table

اگر می‌دانید «سبد Physical و Address غایب → ADDRESS_REQUIRED»، این یک Rule است؛ با Random Pairwise امید نداشته باشید که Oracle را حدس بزند. جدول تصمیم ستون‌های feasible و Actionها را روشن می‌کند.

وقتی Interaction ناشناخته است: t-way

اگر پنج Browser/Locale/Payment/Cart/User عامل دارید و Failureهای تعاملی محتمل‌اند، مدل Pairwise یا Mixed-strength بسازید. Strength را از Risk و داده نقص انتخاب کنید. Pairwise به معنی «بهینه‌ترین» یا «کافی برای همه» نیست.

Constraintهای مدل ترکیبی

Constraint باید قابل Review و Test باشد. هر Constraint تعداد ترکیب‌ها را کم می‌کند و می‌تواند Failure را پنهان کند. منبع، دلیل، Owner و تاریخ انقضا ثبت شود. ترکیب غیرقابل ساخت در Environment با ترکیب نامعتبر کسب‌وکار یک چیز نیست.

Partitioning برای انواع داده

تاریخ و زمان

  • فرمت/Parse: ISO معتبر، فرمت محلی، malformed؛
  • Semantic: تاریخ واقعی/غیرواقعی؛
  • Relation: start<end، برابر، start>end؛
  • State: گذشته، حال، آینده نسبت به Clock ثابت؛
  • Timezone: حاضر/غایب/مجاز/نامجاز؛
  • Calendar: شمسی UI در برابر timestamp قرارداد API.

فایل

  • extension، Media Type و Magic bytes هم‌خوان/متناقض؛
  • محتوا سالم/خراب/رمزدار/چندصفحه‌ای؛
  • اندازه زیر/روی/بالای سقف—مرز با BVA؛
  • نام ساده/Unicode/بسیار بلند/path-like؛
  • Archive کم‌عمق/عمیق/نسبت فشرده‌سازی پرریسک؛
  • اسکن امن/ناموفق/Timeout.

Enum و شناسه

برای Enum: هر عضو با رفتار متفاوت یک Choice، عضو Unknown و case/whitespace جدا بر اساس Parse. برای ID: syntax درست/غلط، موجود/ناموجود/حذف‌شده، متعلق به کاربر/دیگری و مجاز/غیرمجاز Partitionهای متفاوت‌اند.

Collection و ساختار تودرتو

Empty/Single/Multiple، duplicate/unique، ترتیب مهم/نامهم، item معتبر/نامعتبر و عمق ساختار را جدا کنید. «آرایه نامعتبر» معمولاً برای Oracle دقیق بیش از حد کلی است.

Partitionهای مهم برای داده فارسی

رقم و عدد

ASCII 123، فارسی ۱۲۳ و عربی ١٢٣ ممکن است در UI هم‌ارز اما در API متفاوت باشند. Normalization/Coercion را لایه‌به‌لایه مشخص کنید؛ String عددی با JSON number یکی نیست.

حروف و Normalization

ی/ی، ک/ک، فاصله، نیم‌فاصله، شکل Normalized و متن BiDi می‌توانند Search، Unique Constraint و Length را تغییر دهند. Unicode Normalization Forms هم‌ارزی canonical/compatibility را توضیح می‌دهد؛ انتخاب NFC/NFKC یک تصمیم محصول و امنیت است، نه تبدیل کور.

قواعد دامنه ایران

  • شماره موبایل 09...، +98... و مقدار malformed؛
  • کدپستی با صفر ابتدا، طول/نوع و semantic validation؛
  • ریال/تومان، عدد صحیح/رشته نمایشی و separator؛
  • تاریخ شمسی UI و timestamp میلادی/UTC در API؛
  • آدرس تهران/سایر استان/منطقه خارج پوشش؛
  • درگاه پرداخت سالم/ناموفق/Timeout/نتیجه نامعلوم.

به‌جای تقلید داده واقعی مشتری، Representative مصنوعیِ سازگار با قواعد بسازید. راهنمای تولید داده تست Fixture، Seed، Property و Delivery داده را پوشش می‌دهد.

EP و امنیت؛ چه ادعایی مجاز است؟

EP کمک می‌کند ورودی‌های syntactically valid/invalid و semantically valid/invalid را جدا کنیم. OWASP Input Validation می‌گوید Validation باید در Server انجام شود و فقط دفاع اصلی XSS/SQL Injection نیست. Payload حمله یک Threat Test با Oracle امنیتی است، نه صرفاً «Invalid Partition».

Partitionهای امنیتی نمونه

  • مالک منبع / کاربر مجاز دیگر / کاربر غیرمجاز؛
  • Token معتبر / منقضی / scope ناکافی / امضای نامعتبر؛
  • Payload در Limit / بالاتر از Limit / عمق پرریسک؛
  • نام فایل ساده / traversal-like / separatorهای Unicode؛
  • نرخ عادی / Burst / بالاتر از Quota.

Authorization، Encoding، Query parameterization، Rate Limit و Threat Modeling را جدا بسنجید. EP فقط ساختار Test Space را منظم می‌کند.

Property-Based Testing برای آزمودن هم‌ارزی

Hypothesis Strategies عدد، Decimal، Text، Collection، Date و ترکیب‌ها را تولید و Failure را Shrink می‌کند. سه الگوی مفید:

  1. Invariance: جایگزینی عضو داخل Partition Outcome را عوض نکند.
  2. Partition membership: هر داده تولیدشده دقیقاً عضو یک Choice از معیار مربوط باشد.
  3. Cross-partition distinction: داده دو Partition با Behavior متفاوت، Oracle متفاوت بسازد.

Generator نمی‌تواند Specification ناقص را اصلاح کند. نقاط Representative و Boundaryهای بحرانی را Explicit نگه دارید؛ Property-Based مکمل آن‌هاست.

فرآیند گام‌به‌گام EP پیشرفته

  1. Test Objective و رفتار مورد سنجش را محدود کنید.
  2. Test Basisها: نیازمندی، Schema، Rule، State، Incident و Telemetry را جمع کنید.
  3. Factor/Categoryها را بدون مخلوط‌کردن معیارها فهرست کنید.
  4. برای هر Factor، Choiceهای غیرتهی، بدون هم‌پوشانی و در Scope بسازید.
  5. Valid/Invalid، Representative، Oracle، Risk و Owner را ثبت کنید.
  6. Constraint را از Negative Test و Don’t Care جدا کنید.
  7. Each Choice Coverage را به‌عنوان Baseline محاسبه کنید.
  8. Rule معلوم را با Decision Table و Interaction را با t-way تکمیل کنید.
  9. Boundary، State، Security و Operational goals را به تکنیک مناسب بسپارید.
  10. Test Caseها را در Unit/API/UI/Integration به اندازه Risk توزیع کنید.
  11. مدل پوشش را به CI وصل و Uncovered Choice را Fail/Report کنید.
  12. پس از هر Contract change یا Escaped Defect، Partition را Merge/Split/Retire کنید.

اتوماسیون و CI

Model as Code

Factorها و Choiceها را در JSON/YAML یا کد نسخه‌دار نگه دارید. Generator و Test نباید دو فهرست جدا داشته باشند. Lint مدل باید Duplicate، Choice بدون Representative، Constraint بدون دلیل و Outcome نامشخص را گزارش کند.

Coverage Gate

برای Ruleهای پرریسک، Uncovered Partition گیت را Fail کند. برای مدل در حال کشف، نتیجه می‌تواند Warning باشد. گیت Pairwise را با Each Choice یکی نکنید؛ هر معیار Verifier خود را می‌خواهد.

Schema و Rule Diff

تغییر required، Enum، type، Range یا Conditional باید Impact Analysis روی Partition Model ایجاد کند. Version Contract، Build، Dataset و Model را در Run Manifest ذخیره کنید.

Test Laneها

  • Commit: Each Choice کم‌هزینه، Ruleهای بحرانی و Contract؛
  • Nightly: Pairwise/t-way، Property-Based و Unicode گسترده؛
  • Release: Environment/Dependency واقعی، Migration و Risk residual.

وقتی زمان کم است چه کنیم؟

Partition را حذف نکنید و سپس ادعای ۱۰۰٪ نکنید. Coverage کامل مدل را Report و Execution را بر اساس Risk اولویت‌بندی کنید:

  • Impact مالی، امنیتی، قانونی و بازیابی‌ناپذیر؛
  • Likelihood بر اساس Defect/Telemetry، نه حدس عددی بی‌پایه؛
  • Novelty: Rule یا Integration تازه؛
  • Change surface و پیچیدگی Interaction؛
  • Detectability در Production و هزینه Rollback؛
  • اعتماد به Oracle و Fixture.

راهنمای RBT کمک می‌کند عمق و ترتیب تست را به Risk→Evidence→Decision وصل کنید.

نگهداری Partition Model

چه زمانی Split کنیم؟

  • دو عضو یک Partition Outcome متفاوت ساختند؛
  • Rule، Tier، Region یا State جدید رفتار را جدا کرد؛
  • Escaped Defect نشان داد Context پنهانی وجود دارد؛
  • Performance/Security objective جدید، هم‌ارزی قبلی را شکست.

چه زمانی Merge کنیم؟

  • تفاوت تاریخی حذف و Contract یکسان شده است؛
  • Outcome، Risk و مسیر پردازش در چند نسخه یکسان مانده؛
  • تقسیم فقط نام‌های متفاوت و بدون شاهد رفتاری دارد.

چه زمانی Retire کنیم؟

وقتی Input/State دیگر تولید یا پشتیبانی نمی‌شود و داده/Consumer قدیمی مهاجرت شده است. تاریخ، دلیل، Migration evidence و نسخه آخر نگه داشته شود؛ حذف Silent می‌تواند Regression gap بسازد.

معیارهای سالم EP

  • Each Choice Coverage به تفکیک Factor و نسخه؛
  • Pairwise/t-way Coverage جداگانه و فقط روی ترکیب feasible؛
  • Partitionهای بدون Owner/Oracle/Representative؛
  • Escaped Defectهایی که باعث Split یا Choice جدید شدند؛
  • زمان تغییر Contract تا به‌روزرسانی مدل/تست؛
  • نسبت Constraintهای منقضی یا بدون دلیل؛
  • Flaky/Infra Error جدا از Product Failure؛
  • تعداد مدل‌های Over/Under-partition شده پس از Review.

تعداد خام Choice یا Test Case، معیار کیفیت نیست. مدل بزرگ می‌تواند مبهم و مدل کوچک می‌تواند دقیق باشد.

خطاهای رایج و ضدالگوها

  • Valid/Invalid کلی: Missing، null، wrong type و semantic failure یکجا می‌روند.
  • هم‌ارزی مطلق: هدف تست و Context نوشته نمی‌شود.
  • پارتیشن‌های هم‌پوشان: Status و Tier در یک معیار مخلوط می‌شوند.
  • Choice تهی: ترکیب غیرممکن به‌عنوان Case عادی نگه داشته می‌شود.
  • Constraint برای پنهان‌کردن خطا: Payload قابل ارسال از مدل حذف می‌شود.
  • نماینده راحت: Fixture موجود جای Representative معنادار را می‌گیرد.
  • Every Choice = Pairwise: پوشش تک‌عاملی به Interaction تعمیم داده می‌شود.
  • همه ترکیب‌ها: ضرب دکارتی بدون Risk، Constraint و Oracle ساخته می‌شود.
  • Edge case به‌جای مدل: ورودی‌های عجیب بدون Partition و انتظار فهرست می‌شوند.
  • Schema = Business Rule: موجودی، مالکیت و State از قلم می‌افتد.
  • Client-only validation: API خام و Consumer دیگر دور می‌زنند.
  • AI بدون Ground truth: Generator Partition می‌سازد اما Rule/Oracle تأیید نشده است.
  • Coverage بدون مخرج: درصد اعلام می‌شود اما Factor/Choice نسخه‌دار نیست.

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

  • Test Objective و رفتار مورد مقایسه روشن است.
  • هر Factor فقط یک معیار طبقه‌بندی دارد.
  • Choiceها غیرتهی، بدون هم‌پوشانی و در Scope کامل‌اند.
  • Valid/Invalid دارای رفتار و Error Contract دقیق است.
  • Missing/null/empty/blank/wrong type آگاهانه Merge یا Split شده‌اند.
  • هر Choice Representative، Oracle، Risk، Source و Owner دارد.
  • Constraint، Negative Test و Don’t Care جدا هستند.
  • Each Choice Coverage و Uncovered Item قابل محاسبه است.
  • قواعد ترکیبی Decision Table و Interactionها t-way دارند.
  • Boundary، State، Security و Performance به تکنیک مکمل وصل‌اند.
  • داده فارسی، Unicode، رقم، پول و تاریخ Partition شده‌اند.
  • مدل با Schema/Contract/CI نسخه‌دار و پس از Defect بازبینی می‌شود.

جمع‌بندی

بخش‌بندی هم‌ارزی پیشرفته یعنی Test Space پیچیده را به فرضیه‌های رفتاری کوچک، قابل‌ردیابی و قابل‌اندازه‌گیری تبدیل کنیم. Partition خوب غیرتهی و بدون هم‌پوشانی است، نسبت به هدف مشخص هم‌ارزی دارد و با Representative و Oracle روشن سنجیده می‌شود. Each Choice حداقل پایه را می‌دهد، اما Interaction، Boundary، State و Security هرکدام Coverage مستقل می‌خواهند.

مسیر عملی چنین است: Objective → Factor → Partition/Choice → Representative/Oracle → Constraint → Each Choice → Decision/t-way/BVA/State → Automation/Evidence → Split/Merge. هدف کم‌کردن مصنوعی تعداد تست نیست؛ هدف این است که هر تست نماینده یک ادعای روشن درباره رفتار محصول باشد.

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

تفاوت EP و BVA چیست؟

EP دامنه را به Partitionهای رفتاری تقسیم و از هرکدام نماینده می‌گیرد. BVA روی مرز Partitionهای مرتب و همسایه‌هایشان تمرکز می‌کند. EP ابتدا ساختار دامنه را می‌سازد و BVA نقاط حساس لبه را اضافه می‌کند.

Each Choice Coverage چیست؟

وقتی چند Factor داریم، Each Choice الزام می‌کند هر Partition از هر Factor دست‌کم یک‌بار در یک Test Case ظاهر شود. این معیار ترکیب Pairها یا Ruleهای چندشرطی را تضمین نمی‌کند و باید جدا Report شود.

آیا Missing، Null و Empty هم‌ارزند؟

فقط اگر Contract و تمام لایه‌های مرتبط برای هدف تست حاضر رفتار یکسان داشته باشند. JSON Schema، Parser، PATCH semantics و Domain ممکن است آن‌ها را متفاوت پردازش کنند؛ بنابراین پیش‌فرض امن، تحلیل جدا و سپس Merge با شاهد است.

چه زمانی Pairwise را به EP اضافه کنیم؟

وقتی چند Factor دارید و Failure تعاملی محتمل است اما قواعد دقیق همه ترکیب‌ها معلوم نیست. Choiceهای EP ورودی مدل Pairwise می‌شوند. Constraint و Oracle همچنان ضروری‌اند و Pairwise جای Boundary، State یا Decision Table را نمی‌گیرد.

آیا می‌توان Partitioning را خودکار کرد؟

Schema، Enum و Constraint ماشین‌خوان می‌توانند Candidate بسازند و CI پوشش Choiceها را Verify کند. Property-Based Testing نیز عضوهای هر Partition را نمونه‌برداری می‌کند. اما تعریف هم‌ارزی، Risk و Oracle نیازمند Test Basis و قضاوت دامنه است.

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

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