مدل Checkout فقط هفت پارامتر داشت، اما ۹۷۲ ترکیب خام ساخت. پس از اعمال قواعد Wallet، پرداخت در محل و Timeout، هنوز ۲۹۷ ترکیب معتبر باقی ماند. یک Suite زوجی ۱۳ردیفی همه ۱۴۷ تعامل Pairwise معتبر را پوشاند؛ بااینحال فقط ۱۳ مورد از ۲۷ تعامل سهتایی پرریسک Amount×Digits×Payment را دید. سبز بودن «۱۰۰٪ Pairwise» برای این ریسک کافی نبود.
این شکاف، مسئله واقعی طراحی تست ترکیبیاتی یا Combinatorial Test Design است. CTD فقط فشردهکردن یک ماتریس یا تحویل چند ردیف از ابزار نیست؛ باید Input Model درست، Constraint معتبر، Interaction strength متناسب با ریسک، Seed اجباری، Oracle مستقل و معیار پوشش قابلمحاسبه داشته باشیم.
در این راهنما از تعریف تا اجرای واقعی پیش میرویم: فرق Pairwise، t-way و Covering Array را روشن میکنیم؛ مدل Checkout ایرانی را میسازیم؛ با Node.js پوشش را اندازه میگیریم؛ PICT و ACTS را در جای درست بهکار میبریم؛ و نشان میدهیم چرا حذف ردیفهای نامعتبر پس از تولید میتواند ظاهراً Suite را تمیز اما پوشش آن را خراب کند.
طراحی تست ترکیبیاتی چیست؟
طراحی تست ترکیبیاتی روشی برای انتخاب مجموعهای مدیریتپذیر از Test configurationهاست که تعامل مقدارهای پارامترها را تا قدرت مشخص t پوشش دهد. اگر t=2 باشد، هر ترکیب معتبر از مقدارهای هر دو پارامتر باید دستکم در یک ردیف ظاهر شود؛ برای t=3 همین شرط درباره هر سه پارامتر برقرار است.
پاسخ کوتاه
CTD بهجای این پرسش که «چند Test case داریم؟» میپرسد: «کدام Input space را مدل کردهایم، چه تعاملهایی باید پوشش داده شوند و آیا Suite واقعاً آنها را پوشانده است؟» خروجی معمولاً یک Covering Array است؛ هر ردیف آن هنوز باید به Setup، Action، Observation و Expected result قابلاجرا تبدیل شود.
CTD، Pairwise، t-way و Covering Array چه فرقی دارند؟
| اصطلاح | معنا | نکته عملی |
|---|---|---|
| Combinatorial Testing/CTD | خانواده روشهای مدلسازی و پوشش تعاملها | از Scope تا Constraint، تولید، اجرا و سنجش را شامل میشود |
| Pairwise/All-pairs | پوشش ۲-way | Baseline خوب است، نه پاسخ پیشفرض برای هر Risk |
| t-way | پوشش همه تعاملهای معتبر با قدرت t | مثلاً ۳-way یا ۴-way |
| Covering Array | ماتریسی که Interactionهای موردنیاز را در ردیفهای کمتر جا میدهد | کوچکبودن بهتنهایی کیفیت نیست |
| Mixed/Variable Strength | قدرت عمومی کمتر و قدرت بیشتر برای زیرمدل پرریسک | مثلاً ۲-way عمومی و ۳-way برای Payment×Amount×Digits |
| Exhaustive | همه ترکیبهای معتبر مدل | وقتی فضا کوچک یا Risk بحرانی است ممکن است بهترین انتخاب باشد |
برای آموزش مقدماتی All-pairs و یک مثال کوچک، راهنمای تست Pairwise مسیر کوتاهتری است. این مقاله روی طراحی مدل بزرگتر، Mixed-strength، اثبات Coverage و Governance تمرکز دارد.
Covering Array چه چیزی را تضمین میکند؟
اگر مدل، Constraint و Generator درست باشند، یک Covering Array با قدرت t تضمین میکند هر Interaction معتبر از t پارامترِ مدلشده دستکم در یک ردیف حضور دارد. این یک ویژگی ایستای Suite است و درباره اجرای موفق، فعالشدن Branch، درستی Oracle یا نبود Defect تضمین نمیدهد.
- پارامتر حذفشده از مدل، هرگز با Coverage جبران نمیشود؛
- Partition غلط، نماینده نامناسب تولید میکند؛
- Constraint بیشازحد میتواند یک رفتار واقعی را «نامعتبر» اعلام کند؛
- ردیف تولیدشده بدون Setup و داده مناسب ممکن است اصلاً به State هدف نرسد؛
- Oracle ضعیف میتواند تعامل را اجرا کند اما Defect را Pass اعلام کند؛
- ۲-way فقط زوجها را تضمین میکند، نه همه سهتاییها یا Flowها را.
NIST IR ۷۸۷۸ درباره Combinatorial Coverage Measurement نیز Combinatorial coverage را از معیارهای اجرایی مانند Statement/Branch coverage جدا میکند. بنابراین در Dashboard باید «پوشش Interaction» و «شواهد Runtime» دو ستون مستقل باشند.
Interaction Rule را به درصد بازاریابی تبدیل نکنید
پژوهشهای NIST روی چند Domain نشان دادهاند بسیاری از Failureها با تعداد کمی عامل فعال میشوند و با افزایش تعداد عوامل، فراوانی معمولاً کمتر میشود. اما خود صفحه Interactions Involved in Software Failures صریح میگوید Pairwise کافی نیست و هیچ تضمینی برای یافتن تمام Defectها وجود ندارد.
پس عبارتهایی مانند «۷۰٪ باگها ۲-way و ۹۰٪ آنها ۳-way هستند» را بدون Dataset، نوع محصول، مرحله تست و تعریف Failure تکرار نکنید. Fault profile محصول بانکی، Driver، Compiler و اپ محتوایی یکسان نیست. حتی مطالعه تجربی NIST درباره Combinatorial و Random Testing نتیجهای محتاطانه دارد: t-way در اغلب موارد همسطح یا بهتر بود، اما اختلاف همیشه بزرگ نبود و پژوهش بیشتری لازم بود.
ادعای سالمتر
تعاملهای کممرتبه در بسیاری از مطالعات مهم بودهاند؛ Pairwise یک Baseline اقتصادی است، اما Strength نهایی باید از Risk، Fault history، ساختار Rule و آزمایش محلی محصول تعیین شود.
پیش از مدل، CTD Test Contract بنویسید
- System/feature boundary: کدام Component و Version؟
- Question: دنبال Configuration failure، Rule interaction یا Data issue هستیم؟
- Risk: کدام Outcome تجاری/امنیتی/دادهای مهم است؟
- Factor scope: Input، State، Environment و Sequence کداماند؟
- Source: مقدارها و Constraintها از کدام Requirement/Production evidence آمدهاند؟
- Strength policy: قدرت عمومی و زیرمدلهای قویتر چیست؟
- Mandatory seeds: کدام Scenario مستقل از Packing باید حضور داشته باشد؟
- Validity policy: Valid، invalid، unsupported و N/A چگونه جدا میشوند؟
- Oracle: هر Row با چه Expected و Observation قضاوت میشود؟
- Budget: هزینه Setup، اجرا، Reset و Triage هر ردیف چقدر است؟
- Coverage evidence: ابزار و فرمول Verification چیست؟
- Owner/expiry: مدل چه زمانی و توسط چه کسی بازبینی میشود؟
Input Model را چگونه بسازیم؟
Input Model فهرست تصادفی Dropdownهای UI نیست. هر Factor باید بتواند رفتار یا مسیر اجرا را تغییر دهد و هر Value باید یک Partition معنادار از Domain باشد. نامها نیز باید Semantic باشند: amount=max_approved بهتر از amount=3 است.
پارامترها را از چهار لایه پیدا کنید
- Business input: نوع مشتری، مبلغ، روش پرداخت، نوع سفارش؛
- System state: موجودی، عضویت، Session، Feature flag، Migration version؛
- Environment/configuration: OS، Browser engine، Locale، Dependency version؛
- Operational condition: Timeout، Retry، Queue lag، Clock boundary.
Factorهای مشتقشده را دوباره وارد نکنید. اگر is_high_value دقیقاً از amount_band محاسبه میشود، حضور همزمان هر دو میتواند Interaction مصنوعی و Constraint اضافی بسازد. Factor مشتقشده فقط وقتی مستقل شود که رفتار SUT آن را جداگانه دریافت یا ذخیره میکند.
Valueها را با EP و BVA انتخاب کنید
پارامتر پیوسته را بدون دلیل به صدها عدد نشکنید. ابتدا بخشبندی همارزی را برای رفتارهای متفاوت و سپس تحلیل مقدار مرزی را برای نمایندههای مرزی اجرا کنید. NIST در FAQ رسمی Combinatorial Testing نیز توصیه میکند مقدارهای پیوسته بر اساس Requirement Partition شوند و تعداد Valueها بیدلیل بزرگ نشود.
برای مبلغ ریالی، min باید دقیقاً به یک مقدار و Rule نسخهدار نگاشت شود. «کم/متوسط/زیاد» بدون آستانه، Expected و Unit مدل قابلبازتولید نیست. تومان نمایشی را نیز با ریال Canonical یکی نگیرید.
Missing، Null و N/A را قاطی نکنید
missing: Field ارسال نشده است؛null: Field حاضر اما بدون مقدار است؛empty: مقدار رشتهای یا Collection خالی است؛invalid: مقدار خارج از Contract است؛N/A: آن Factor در این Scenario معنا ندارد.
N/A یک داده واقعی SUT نیست؛ یک تصمیم مدلسازی است. اگر وارد خروجی میشود، Runner باید بداند آن را حذف کند، نه اینکه رشته "N/A" را به API بفرستد.
Constraint را داخل Generation اعمال کنید
Constraint مشخص میکند کدام Assignment در Domain معتبر یا قابلاجراست. سه منبع آن معمولاً Product rule، محدودیت Platform و محدودیت Test environment است. این سه را با Label جدا کنید تا محدودیت آزمایشگاه بهاشتباه Requirement محصول نشود.
مثال Constraint قابلردیابی
- Wallet فقط برای Member معتبر است؛ Source: WALLET-RULE-۴؛
- COD در Timeout-after-commit معنا ندارد؛ Source: PAYMENT-FLOW-۷؛
- وقتی Network=OK است Retry key کاربرد ندارد؛ مدل آن را N/A میکند؛
- Android نسخه قدیمی فقط در Legacy lane اجرا میشود؛ این Environment policy است، نه Product truth.
مستند رسمی PICT هشدار میدهد ردیف نامعتبر را پس از Generation حذف نکنید، چون همان ردیف ممکن است تنها پوشش برخی Pairهای معتبر را حمل کند. Constraint باید هنگام ساخت Suite وارد شود تا Generator تعاملهای معتبر ازدسترفته را در ردیفهای دیگر دوباره Pack کند.
Forbidden با Negative Test فرق دارد
اگر ترکیب از نظر Business ممنوع است اما کاربر/مهاجم میتواند آن را ارسال کند، حذف کامل آن اشتباه است. آن را از Model مثبت خارج و در Model منفی با Expected rejection، Authorization یا Validation مناسب تست کنید. «نامعتبر» یعنی Pass انتظار متفاوتی دارد، نه اینکه همیشه ارزش تست ندارد.
Interaction Strength را بر اساس Risk انتخاب کنید
| Strength | کاربرد محتمل | محدودیت |
|---|---|---|
| 1-way | هر Value دستکم یکبار؛ Smoke/Inventory | تعاملها را نمیسنجد |
| 2-way | Baseline وسیع برای Configuration و Input | Ruleهای سهعاملی را تضمین نمیکند |
| 3-way | زیرسیستم پرریسک، Fault history چندعاملی | Suite بزرگتر میشود |
| 4–6-way | بخش محدود و بحرانی با شواهد ریسک | هزینه Value و Constraint بسیار حساس است |
| Exhaustive | زیرمدل کوچک یا Rule حیاتی | برای کل فضای بزرگ عملی نیست |
برای هر Factor یک Strength یکسان انتخاب نکنید. اگر Interaction خطرناک فقط میان payment×amount×digits است، قدرت سراسری ۳ روی هفت یا هفتاد پارامتر هزینه غیرضروری میسازد. Mixed-strength همان سهتایی را کامل میکند و بقیه Model را ۲-way نگه میدارد. صفحه ابزارهای رسمی NIST نیز Constraint و Variable-strength را از قابلیتهای ACTS معرفی میکند.
Seed، Weight و Coverage را یکی نگیرید
- Seed: ردیفی است که باید حتماً در Suite باشد؛ مانند Incident گذشته یا Contract example؛
- Weight: ترجیح Generator برای حضور بیشتر یک Value است و ممکن است تضمین سخت نباشد؛
- Coverage requirement: Interactionی است که باید از نظر ریاضی پوشش داده شود؛
- Priority: ترتیب اجرای Rowها بر اساس Risk/Feedback time است.
Seed را برای Scenarioهای حیاتی استفاده کنید، اما دهها تست تصادفی قدیمی را بیدلیل Seed نکنید؛ آنها Packing را بزرگ و مدل را غیرقابلفهم میکنند. هر Seed باید Source، Reason، Owner و تاریخ بازبینی داشته باشد.
آزمایش واقعی: CTD برای Checkout ایرانی
یک مدل کنترلشده با Node.js ۲۴.۱۸.۰ ساختیم. این مدل ادعای بهینهبودن حداقل تعداد ردیفها ندارد؛ از Greedy set cover قطعی استفاده میکند تا تفاوت Coverageها را قابلبازتولید نشان دهد.
| پارامتر | مقدارها | ریسک نمونه |
|---|---|---|
| channel | web, android, ios | تفاوت Client و Serialization |
| account | guest, member | Wallet و مجوز |
| amount | min, normal, max | مرز کارمزد/سقف |
| digits | latin, persian, arabic | ۰۱۲۳ / ۰۱۲۳ / ۰۱۲۳ |
| payment | ipg, wallet, cod | Flow و Settlement متفاوت |
| network | ok, timeout_after_commit | نتیجه مبهم پس از Commit |
| retry_key | na, same, new | Idempotency و Duplicate charge |
Constraintها: Wallet فقط Member؛ COD فقط Network=OK؛ در حالت OK مقدار Retry برابر N/A؛ و در Timeout مقدار Retry باید Same یا New باشد. Seed اجباری نیز Android×Member×Max×Persian×IPG×Timeout×New بود.
const parameters = {
channel: ["web", "android", "ios"],
account: ["guest", "member"],
amount: ["min", "normal", "max"],
digits: ["latin", "persian", "arabic"],
payment: ["ipg", "wallet", "cod"],
network: ["ok", "timeout_after_commit"],
retry_key: ["na", "same", "new"]
};
function valid(v) {
if (v.payment === "wallet" && v.account !== "member") return false;
if (v.payment === "cod" && v.network !== "ok") return false;
if (v.network === "ok" && v.retry_key !== "na") return false;
if (v.network === "timeout_after_commit" && v.retry_key === "na") return false;
return true;
}
خروجی اجرای کنترلشده
node=v24.18.0
raw_cartesian=972
valid_after_constraints=297
valid_pair_requirements=147
pairwise_rows=13
pairwise_pair_coverage=147/147
pairwise_risky_triple_coverage=13/27
mixed_strength_rows=27
mixed_pair_coverage=147/147
mixed_risky_triple_coverage=27/27
seed_preserved=true
unconstrained_rows=15
rows_left_after_post_filter=4
post_filter_pair_coverage=72/147
post_filter_missing_pairs=75
نتیجه اول: کاهش ۲۹۷ ترکیب معتبر به ۱۳ Row، Pairwise کامل ساخت؛ اما «Pairwise=۱۰۰%» فقط ۱۳/۲۷ سهتایی پرریسک را پوشاند. با Mixed-strength تعداد ردیفها ۲۷ شد و هم ۱۴۷/۱۴۷ Pair و هم ۲۷/۲۷ Triple پوشش گرفت.
نتیجه دوم: Generator بدون Constraint پانزده Row ساخت. پس از حذف ردیفهای نامعتبر فقط چهار Row باقی ماند و Coverage معتبر به ۷۲/۱۴۷ سقوط کرد؛ ۷۵ Pair گم شد. تعداد ردیف کمتر در اینجا Optimization نبود، تخریب شواهد بود.
Coverage را مستقل Verify کنید
function verify(suite, requiredInteractions) {
const seen = new Set();
for (const row of suite) {
for (const interaction of interactionsCoveredBy(row)) {
seen.add(interaction);
}
}
const missing = requiredInteractions.filter((item) => !seen.has(item));
return {
covered: requiredInteractions.length - missing.length,
required: requiredInteractions.length,
missing
};
}
در Production، Coverage verifier باید همان Model version و Constraint semantics را بخواند، اما از خروجی Generator مستقل باشد. اگر ابزار فقط عبارت «Generation successful» نشان دهد، هنوز Evidence کافی برای ۱۰۰٪ Coverage نداریم.
کمترین تعداد ردیف همیشه هدف درست نیست
ساخت Covering Array کوچک یک مسئله Packing دشوار است و Generatorها معمولاً از Heuristic استفاده میکنند. دو Seed تصادفی یا دو Algorithm میتوانند تعداد Row متفاوت اما Coverage یکسان بسازند. مستند PICT نیز توضیح میدهد Seedهای مختلف ممکن است اندازه Suite را تغییر دهند، بدون اینکه معیار پوشش ضعیف شود.
برای انتخاب Suite فقط تعداد Row را مقایسه نکنید:
- هزینه Provision و Reset هر ردیف؛
- تعداد Configuration switchهای گران؛
- قابلیت اجرای موازی؛
- حضور Seedهای Risk-based؛
- سادگی Triage و Reproduction؛
- پایداری خروجی بین نسخههای مدل؛
- Coverage مستقل و قابلاثبات.
گاهی Suite هجدهردیفی که تغییر Device را گروهبندی میکند، در عمل ارزانتر از Suite سیزدهردیفی با Switch مداوم Environment است. Optimization باید بر Cost of evidence انجام شود، نه عدد ردیف.
PICT را چگونه بهکار ببریم؟
PICT ابزار خط فرمان Microsoft برای تولید Configurationهای Combinatorial است. Model از Parameter، Sub-model و Constraint ساخته میشود؛ پیشفرض آن Pairwise است و با /o:N میتوان Strength بالاتر تعیین کرد. خروجی Tab-separated را میتوان مستقیماً به Data-driven runner داد.
Channel: web, android, ios
Account: guest, member
Amount: min, normal, max
Digits: latin, persian, arabic
Payment: ipg, wallet, cod
Network: ok, timeout_after_commit
RetryKey: na, same, new
IF [Payment] = "wallet" THEN [Account] = "member";
IF [Payment] = "cod" THEN [Network] = "ok";
IF [Network] = "ok" THEN [RetryKey] = "na";
IF [Network] = "timeout_after_commit" THEN [RetryKey] <> "na";
Model را کنار کد Version کنید و Command، Tool version و Random seed را در Manifest نگه دارید. Weight در PICT تضمین فراوانی نیست؛ اگر یک Scenario باید حتماً حضور داشته باشد از Seeding استفاده کنید و بعد Coverage را دوباره بسنجید.
ACTS چه زمانی مناسبتر است؟
ACTS محصول پژوهشی NIST برای تولید t-way است و از Constraint، Variable-strength و GUI/CLI پشتیبانی میکند. برای تیمی که Mixed-strength، Coverage measurement یا مدلهای بزرگ پژوهشی میخواهد، گزینه مهمی است. صفحه Tutorials and Documentation رسمی ACTS راهنمای کاربر، API و ابزار سنجش Coverage را ارائه میکند.
نام ابزار جای PoC را نمیگیرد. یک مدل واقعی کوچک را وارد کنید و این پرسشها را بسنجید:
- زبان Constraint تمام Ruleهای لازم را بدون دورزدن بیان میکند؟
- Mixed/Variable strength و Seed موردنیاز را پشتیبانی میکند؟
- Coverage را مستقل Export/Measure میکند؟
- Output پایدار، Machine-readable و مناسب CI است؟
- Unsatisfiable constraint و Value بدون پوشش را واضح گزارش میکند؟
- نسخه، License، Dependency و مسیر نگهداری روشن است؟
ابزار CTD را بر اساس مسئله انتخاب کنید
| نیاز | گزینه | اعتبارسنجی ضروری |
|---|---|---|
| CLI ساده و مدل متنی | PICT | Constraint، Seed، Strength و Parser خروجی |
| Variable-strength و اکوسیستم NIST | ACTS | نسخه Java، CLI/API و Coverage tool |
| Generator سریع/Web GUI | CAgen | Constraint semantics و Export reproducible |
| Suite موجود | Coverage Measurement | Model/Constraint همنسخه |
| Pipeline سفارشی | Library/Generator داخلی | Known arrays، Property tests و Independent verifier |
راهنمای Practical Combinatorial Testing از NIST مرجع گستردهای برای Model، Covering Array، کاربردها و Sequence coverage است. بااینحال نسخه و قابلیت واقعی ابزار انتخابی را در محیط خودتان Pin و اجرا کنید.
هر Row را به تست قابلاجرا تبدیل کنید
ردیف android/member/max/persian/ipg/timeout/new هنوز Test case نیست. باید این اجزا را اضافه کنید:
- Test ID و Model/Row ID؛
- Setup دقیق Account، Balance، Feature flag و PSP stub؛
- Concrete value نگاشتشده به Partitionها؛
- Action و Correlation/Idempotency ID؛
- Oracle source و Expected response/state/side effect؛
- Observation point در API، Ledger، Queue و Audit؛
- Cleanup/Reset و Isolation؛
- Evidence، Verdict و Link به Covered interactions.
قالب اجرایی را از راهنمای نوشتن تستکیس بگیرید و منبع Expected، Comparator و Verdict را با راهنمای طراحی Test Oracle کنترل کنید. CTD فقط «چه ترکیبی» را انتخاب میکند؛ درباره «درست چیست» تصمیم نمیگیرد.
Constraint test و Negative model جدا بسازید
Suite مثبت باید Assignmentهای مجاز را پوشش دهد. برای ورودی نامعتبر یک Model مکمل بسازید که در هر Row تعداد محدودی Violation کنترلشده دارد تا Error masking رخ ندهد. اگر هم Amount نامعتبر، هم Token خراب و هم Method ممنوع باشد، اولین Validation مانع مشاهده دو خطای دیگر میشود.
| نوع | مثال | Expected |
|---|---|---|
| Valid constraint | Wallet + Member | Flow کیف پول اجرا شود |
| Unsupported | COD + Timeout simulation | در Model مثبت N/A؛ نه Product request |
| Business invalid | Wallet + Guest | رد قابلپیشبینی و بدون Side effect |
| Malformed | Digits ترکیبی/Overflow | Validation امن و Traceable |
| Unauthorized | Wallet متعلق به کاربر دیگر | عدم افشای داده و عدم برداشت |
برای Ruleهای منطقی از Decision Table کمک بگیرید
CTD و Decision Table رقیب نیستند. جدول تصمیم مشخص میکند کدام ترکیب Conditionها به کدام Action منجر میشود؛ CTD میتواند Environment/Data Variation پیرامون Ruleها را فشرده کند. اگر هر Rule Outcome حقوقی یا مالی مستقل دارد، ابتدا Decision Table را کامل کنید و Ruleهای بحرانی را به Seed یا Mixed-strength تبدیل کنید.
Constraint نباید Rule مورد آزمون را حذف کند. اگر میخواهیم بررسی کنیم Guest با Wallet رد میشود، عبارت «Wallet فقط Member است» در مدل مثبت درست است اما برای تست همان Validation باید Model منفی یا Rule table جدا داشته باشیم.
Configuration Matrix را با CTD فشرده کنید
Browser×OS×Device×Locale×Theme×Auth×Network بهسرعت بزرگ میشود. ابتدا Support policy را تعیین کنید، سپس برای Combinationهای Supported از CTD استفاده کنید. راهنمای طراحی Browser و Device Matrix درباره Engine، WebView، دستگاه واقعی و سهم کاربر مرز دقیقتری دارد.
- Top production configurationها را Seed کنید؛
- ترکیبهای Unsupported را از Invalid behavior جدا کنید؛
- Deviceهای گران را در Sub-model گروهبندی کنید؛
- Locale فارسی، RTL، Font و Digit shape را Factor مستقل و معنادار بسازید؛
- Coverage منطقی را با Execution capability آزمایشگاه یکی نگیرید.
Value Combination با Event Sequence یکی نیست
Covering Array معمولی تضمین میکند مقدارها در یک Configuration همزمان ظاهر شوند؛ ترتیب رخدادها را پوشش نمیدهد. Retry قبل از Callback، Logout پس از Token refresh یا Add→Remove→Undo مسئله Sequence است. صفحه Event Sequence Testing در NIST Sequence Covering Array را برای پوشش ترتیبهای t-way معرفی میکند.
برای Flow پرداخت، Configuration و Sequence را دو لایه کنید:
- CTD انتخاب میکند Channel، Amount، Digits، Payment و Network چه باشند؛
- State/sequence model تعیین میکند Initiate→PSP commit→Timeout→Retry→Callback→Reconcile با چه ترتیبهایی اجرا شود؛
- Oracle روی عدم Duplicate charge، State نهایی و Audit evidence قضاوت میکند.
چند Input Model مشترک را Copy/Paste نکنید
وقتی Mobile، Web و API پارامترهای مشترک اما Constraintهای متفاوت دارند، سه فایل Copyشده بهسرعت Drift میکنند. Source تعریف مشترک را نسخهدار و Overrideها را صریح کنید. پژوهش NIST درباره تولید ترکیبیاتی برای چند Input Model با پارامتر مشترک نیز دقیقاً این مسئله را بررسی میکند.
در عمل یک Registry کوچک برای Factorها مفید است: Canonical name، Domain، concrete mapper، owner، source version و مصرفکنندهها. تغییر digits از دو Choice به سه Choice باید Impact تمام Modelهای مصرفکننده را نشان دهد.
داده تست را از Row جدا اما Traceable نگه دارید
Row میگوید amount=max؛ Data resolver باید برای Rule نسخه ۷ آن را به عدد دقیق ریال نگاشت کند. نگاشت باید Deterministic و در Artifact ثبت شود. برای Account/Wallet/Order نیز داده مصنوعی یا رزروشده، Isolation و Cleanup لازم است؛ جزئیات در راهنمای مدیریت داده تست آمده است.
- هر Partition به Concrete value و Source rule نگاشت شود؛
- PII و PAN واقعی برای افزایش «واقعگرایی» کپی نشود؛
- Row ID در داده، Log و Report Correlate شود؛
- Factory change بدون Model impact review منتشر نشود؛
- Clock، Randomness و Dependency behavior قابلکنترل باشد.
مدل ایرانی را واقعاً بومی کنید
افزودن یک Value به نام fa کافی نیست. Interactionهای محتمل محصول ایرانی را از Incident و Domain استخراج کنید:
- ریال Canonical در برابر تومان نمایشی و مرز گردکردن؛
- ارقام Latin/Persian/Arabic و رشته دارای Separator؛
- ی/ی، ک/ک، ZWNJ، Bidi و نام طولانی پذیرنده؛
- UTC در Backend، Asia/Tehran در UI و تاریخ جلالی در نمایش؛
- IPG/Wallet/COD و PSPهای دارای Callback متفاوت؛
- Timeout قبل یا بعد از Commit، Retry key و Callback تکراری؛
- شبکه متغیر Mobile و Resume پس از Background؛
- Feature flag، Tenant، Role و کانال Supportشده.
همه را در یک مدل عظیم نریزید. مدل Parse/Unicode، مدل Payment resilience و مدل Compatibility میتوانند جدا باشند و چند Scenario End-to-end پرریسک آنها را به هم وصل کند.
CTD را در CI/CD چگونه اجرا کنیم؟
| Lane | Suite | هدف |
|---|---|---|
| PR | Seedها + Risk-weighted ۱/۲-way سریع | Feedback کوتاه و تغییر مرتبط |
| Nightly | Pairwise کامل همه Modelهای فعال | Coverage سراسری |
| Release | Mixed-strength و Configuration واقعی | تعاملهای پرریسک |
| Periodic | ۳/۴-way محدود، Sequence و Fault injection | کشف Interactionهای عمیق |
| Incident | Reproducer بهعنوان Seed | جلوگیری از Regression |
Generation میتواند با تغییر Model در CI اجرا شود؛ Suite تولیدشده را همراه Hash مدل و ابزار Artifact کنید. اگر تعداد Row ناگهان نصف شد، آن را موفقیت فرض نکنید: Constraint diff، Required interaction count و Coverage report باید Review شوند.
Manifest قابلممیزی بسازید
- Model ID/version/hash و Product build؛
- Parameter/Value source versions؛
- Constraint IDs و Owners؛
- Global و Mixed strength policy؛
- Generator name/version/options/random seed؛
- Mandatory seed list و دلیل؛
- Raw/valid configuration counts؛
- Required/covered/missing interactions؛
- Concrete data resolver version؛
- Runner/Oracle versions و Environment؛
- Row results، Inconclusiveها و Evidence location؛
- Approver، expiry و superseded model.
Coverage Report سالم چه دارد؟
عبارت «۲-way کامل» باید با عدد و Scope همراه باشد: 147/147 valid pairs for model checkout-v7. برای Mixed-strength نیز هر Requirement group جدا گزارش شود. Coverage denominator باید پس از Constraint محاسبه شود، نه از Cartesian خام.
- ۱-way و t-way Coverage به تفکیک Model؛
- Valid interaction count و Excluded interaction count با Reason؛
- Seed coverage و Seed execution result؛
- Row executable/pass/fail/inconclusive؛
- Branch/State/Requirement coverage بهعنوان معیار Runtime مستقل؛
- Fault/Mutant detection برای سنجش قدرت واقعی؛
- Stale Model/Constraint/Mapper count؛
- Cost per useful verdict.
Failing Row را چگونه Triage کنیم؟
یک Row شکستخورده ثابت نمیکند کدام Pair یا Triple علت است. ابتدا Failure را Reproduce و Environment/Data issue را حذف کنید؛ سپس با تغییر کنترلشده یک Factor در هر نوبت یا تولید Follow-up covering array، فرضیه Interaction را محدود کنید.
- Row، Concrete data، Build و Oracle را Freeze کنید؛
- Failure signature و Side effect را ثبت کنید؛
- تمام Interactionهای Row را Candidate بدانید، نه Cause قطعی؛
- از Neighbor tests و Minimal failure-inducing combination کمک بگیرید؛
- پس از Root cause، یک تست متمرکز و Seed Regression اضافه کنید؛
- Fault profile را برای تصمیم Strength آینده بهروز کنید.
متریکهای مفید CTD
- Required/covered valid interactions به تفکیک Strength؛
- Executable rows / generated rows؛
- Invalid-after-generation rows؛ هدف باید صفر باشد؛
- Model omission defects و Constraint defects؛
- Interaction-related escaped defects؛
- Faults found per ۱۰۰ executed rows، با Context؛
- Seed pass/fail و Seed staleness؛
- Suite generation/execution/triage cost؛
- Configuration switch cost و Parallel efficiency؛
- Model change تا Coverage restoration time؛
- Oracle inconclusive/false-fail rate؛
- Risky subgroup 3/4-way coverage.
Reduction ratio بهتنهایی قابلبازی است. حذف Value و Constraint بیشازحد Ratio را زیبا میکند اما Risk را پنهان میسازد.
ضدالگوهای طراحی تست ترکیبیاتی
- هر Dropdown یک Factor: بدون ارتباط با رفتار.
- Pairwise برای همهچیز: بدون Risk/Fault profile.
- درصدهای جهانی Defect: بدون Dataset و Domain.
- حذف Row نامعتبر بعد از Generation: Coverage سوراخ میشود.
- Constraint بهجای Validation test: رفتار ممنوع هرگز تست نمیشود.
- Valueهای پیوسته فراوان: Explosion بدون معنای بیشتر.
- N/A بهعنوان ورودی واقعی: Runner داده جعلی ارسال میکند.
- Coverage=Quality: Oracle و Runtime گم میشوند.
- کمترین Row به هر قیمت: Setup/Triage گرانتر میشود.
- Seed بدون Owner: Suite به موزه Regression تبدیل میشود.
- مدل Copy/Paste: Constraintها Drift میکنند.
- همه ابعاد در یک Mega-model: Scope و تفسیر از بین میرود.
- Sequence بهعنوان Parameter: ترتیب واقعی تضمین نمیشود.
- Random rows بهنام CTD: Required interaction list وجود ندارد.
- Generator success بهنام Verification: Coverage مستقل محاسبه نشده است.
برنامه ۳۰روزه پیادهسازی CTD
هفته اول: Scope و مدل کوچک
- یک Flow با انفجار واقعی انتخاب کنید؛
- Incident و Requirement را برای Factor/Constraint مرور کنید؛
- EP/BVA را اجرا و Model v1 بسازید؛
- Cartesian/valid count را محاسبه کنید.
هفته دوم: Generation و Verification
- PICT یا ACTS را با Model واقعی PoC کنید؛
- Pairwise baseline و Coverage report بسازید؛
- سه Risk را Seed و یک subgroup را Mixed-strength کنید؛
- Post-filter و unsatisfied constraint را Gate کنید.
هفته سوم: اجرا و Oracle
- Row mapper و Synthetic data factory بسازید؛
- State/side-effect Oracle اضافه کنید؛
- PR و Nightly lane را جدا کنید؛
- Failure triage و Evidence schema را تمرین کنید.
هفته چهارم: حاکمیت و یادگیری
- Model/Constraint owner و expiry تعیین کنید؛
- Coverage، Cost و Fault profile را Baseline کنید؛
- یک Incident قدیمی را به Seed تبدیل کنید؛
- Strength و Valueها را از شواهد Retrospective اصلاح کنید.
چکلیست بازبینی CTD
- □ Scope، سؤال و Risk مدل روشن است.
- □ Factorها مستقل، رفتارساز و دارای Source هستند.
- □ Valueها با EP/BVA و Concrete mapper تعریف شدهاند.
- □ Unit پول، Locale، State و Environment مبهم نیست.
- □ Constraintها Source/Owner/Type دارند.
- □ Negative behavior بهاشتباه حذف نشده است.
- □ Global strength از Risk انتخاب شده است.
- □ Subgroupهای پرریسک Mixed-strength دارند.
- □ Incident/Contract scenarioها Seed شدهاند.
- □ Generator version/options/seed ثبت شدهاند.
- □ Coverage مستقل و بعد از Constraint محاسبه شده است.
- □ هیچ Row پس از Generation بیصدا حذف نمیشود.
- □ هر Row Setup، Data، Oracle و Cleanup دارد.
- □ Sequence risk با مدل ترتیبی مکمل پوشش داده میشود.
- □ Combinatorial و Runtime coverage جدا گزارش میشوند.
- □ Suite بر Cost اجرا و Triage نیز بهینه شده است.
- □ Model diff در CI Review و Gate میشود.
- □ Owner، Expiry و Retrospective تعریف شدهاند.
سؤالات متداول طراحی تست ترکیبیاتی
طراحی تست ترکیبیاتی چه تفاوتی با Pairwise دارد؟
Pairwise فقط حالت ۲-way از CTD است. طراحی ترکیبیاتی علاوه بر آن شامل Input Model، Constraint، انتخاب Strength، Mixed-strength، Seed، Generation، Coverage verification، تبدیل Row به تست اجرایی و Governance میشود.
آیا پوشش ۱۰۰٪ Pairwise یعنی تست کافی است؟
خیر. فقط میگوید همه زوجهای معتبرِ مدلشده حضور دارند. در آزمایش این مقاله Pairwise برابر ۱۴۷/۱۴۷ بود، اما فقط ۱۳/۲۷ سهتایی پرریسک را پوشاند. Parameter جاافتاده، Sequence، Oracle ضعیف و Defect با Strength بالاتر همچنان باقی میمانند.
Strength مناسب را چگونه انتخاب کنیم؟
با ۲-way بهعنوان Baseline شروع کنید و بر اساس Risk، Fault history، Rule complexity، ساختار کد و آزمایش محلی برای subgroupهای مشخص ۳-way یا بیشتر بسازید. افزایش سراسری t بدون تحلیل معمولاً هزینه را بهشدت بالا میبرد.
با ترکیبهای نامعتبر چه کنیم؟
Constraintهای Domain را هنگام Generation اعمال کنید؛ ردیفها را بعداً حذف نکنید. ترکیبهایی که کاربر واقعاً میتواند ارسال کند اما باید Reject شوند، در Negative model جدا با Oracle خطا، امنیت و عدم Side effect تست شوند.
PICT بهتر است یا ACTS؟
PICT برای CLI و Model متنی ساده مناسب است؛ ACTS امکانات پژوهشی NIST مانند Variable-strength و ابزارهای Coverage را ارائه میکند. انتخاب به Constraint، CI/API، اندازه مدل، قابلیت بازتولید و نیاز Coverage شما بستگی دارد؛ با مدل واقعی PoC کنید.
جمعبندی
CTD هنر کمکردن Test case نیست؛ مهندسی یک ادعای پوشش محدود و قابلممیزی است. باید بدانیم چه فضایی مدل شده، کدام تعاملها لازماند، کدام Assignmentها معتبرند، هر Row چگونه اجرا و قضاوت میشود و چه Riskی بیرون میماند.
آزمایش Checkout نشان داد ۱۳ Row میتواند همه ۱۴۷ Pair معتبر را پوشش دهد، اما فقط ۱۳ از ۲۷ Triple پرریسک را ببیند. Mixed-strength با ۲۷ Row شکاف را بست و Seed را حفظ کرد؛ در مقابل، حذف ردیفهای نامعتبر پس از تولید ۷۵ Pair را نابود کرد. معیار تصمیم روشن است: Suite خوب کوچکترین فایل نیست؛ کوچکترین مجموعهای است که پوشش موردنیاز، اجرای واقعی و Verdict قابلاعتماد را با هزینه پذیرفتنی فراهم میکند.

