نسخه جدید Checkout در یک اجرا زیر دو ثانیه پاسخ داد؛ سبز. در ۲۰ اجرا هم هیچ مورد کندی دیده نشد؛ باز هم سبز؟ نه. با وجود نرخ مشاهدهشده صفر، کران بالای بازه اطمینان ۹۵٪ هنوز ۱۶٫۱۱۳٪ بود. برای قراردادی که سهم پاسخهای کند را حداکثر ۱٪ میداند، Verdict درست «نامشخص» است، نه Pass.
این تفاوت، قلب تست سیستمهای غیرقطعی است. وقتی Randomness، Scheduler، Network، External service، Clock یا Sampling روی خروجی اثر میگذارد، یک Actual دقیق و یک Retry ساده شواهد کافی نیست. باید مشخص کنیم چه چیزی را کنترل میکنیم، چه چیزی را عمداً Randomize میکنیم، واحد Trial چیست، Population هدف کدام است و با چه قاعدهای از مجموعه اجراها به Pass، Fail یا Inconclusive میرسیم.
در این راهنما Nondeterminism محصول را از Flaky test جدا میکنیم؛ یک Experimental Contract میسازیم؛ Seed و Replay را بدون وعده کاذب بهکار میبریم؛ Concurrency، Eventual consistency، زمان و Dependency را تست میکنیم؛ و با یک اجرای واقعی Node.js نشان میدهیم چرا «۲۰ بار سبز شد» هنوز ممکن است هیچ ادعای آماری معتبری نسازد.
سیستم غیرقطعی چیست؟
یک سیستم نسبت به مجموعهای از Input، State و Environment غیرقطعی است اگر تکرار ظاهراً یک شرایط بتواند چند Behavior مجاز یا مشاهدهشده بسازد. واژه «نسبت به» مهم است: اگر Seed، Clock، Scheduler state یا پاسخ Dependency را از Test input حذف کنیم، سیستمی غیرقطعی به نظر میرسد که شاید با ثبت آن عوامل کاملاً قابل Replay باشد.
پاسخ کوتاه
Nondeterminism یعنی یک اجرای منفرد نماینده کامل رفتار نیست. راهحل همیشه حذف تنوع نیست؛ گاهی باید منبع را کنترل و Replay کرد، گاهی چند Schedule را جستوجو کرد، گاهی Distribution را برآورد کرد و گاهی فقط Invariantهایی را سنجید که در تمام خروجیهای مجاز باید برقرار باشند.
چند خروجی با رفتار غلط یکی نیست
| وضعیت | مثال | قضاوت |
|---|---|---|
| تنوع مجاز | ترتیب دو پیشنهاد همامتیاز | Set/Property oracle |
| تنوع بودجهدار | کمتر از ۱٪ پاسخ بالای ۲ ثانیه در Workload مشخص | Statistical verdict |
| رفتار وابسته به State پنهان | Cache warm/cold یا Feature flag | State را آشکار و Partition کنید |
| Race defect | گاهی دو برداشت برای یک Idempotency key | هر نقض Invariant، Fail |
| Flaky harness | Assertion پیش از رسیدن Event | Test defect؛ اصلاح Synchronization |
| Dependency incident | Sandbox PSP ناپایدار | Environment/Dependency verdict جدا |
| مشاهده ناکافی | Trace sampling مسیر Fail را حذف کرده | Inconclusive؛ Evidence ناقص |
برای Invariant مالی، «احتمال Duplicate کم بود» معیار قبولی نیست. هیچ Sample محدودی نبود خطا را اثبات نمیکند، اما یک نقض ثبتشده برای Fail کافی است. Statistical budget را فقط برای Outcomeهایی تعریف کنید که Product/Risk owner واقعاً بهصورت احتمالاتی پذیرفته است.
منابع Nondeterminism را طبقهبندی کنید
Randomness عمدی
Shuffle، Sampling، Load balancing، Backoff jitter، بازی و الگوریتم Randomized از PRNG یا منبع تصادف استفاده میکنند. Seed میتواند مسیر PRNG را Replay کند، اما فقط وقتی نسخه Algorithm، تعداد و ترتیب فراخوانیها و همه منابع تصادف ثابت باشند.
Concurrency و Scheduler
Thread/Goroutine/Actorها ممکن است Interleaving متفاوت داشته باشند. Race، Deadlock، Lost update و Visibility defect از ترتیب دسترسیها میآیند. Seed یک Generator لزوماً Schedule واقعی OS را کنترل نمیکند.
زمان و Timeout
Wall clock، Monotonic clock، Timer resolution، Queue wait، GC pause، NTP correction و مرز Timezone میتوانند مسیر را عوض کنند. Sleep ثابت، زمان را کنترل نمیکند؛ فقط یک حدس را کندتر میکند.
سیستم توزیعشده و Dependency
Network delay/loss/reorder، Replica lag، Retry، Failover، Callback تکراری و پاسخ سرویس بیرونی State قابلمشاهده را تغییر میدهند. این تنوع ممکن است مجاز باشد، اما Contract نهایی مانند عدم Duplicate یا رسیدن به State سازگار باید روشن بماند.
Data و Model drift
داده زنده، Index تازه، Feature store، Model version یا Ranking candidate میتواند بین دو اجرا عوض شود. این موضوع در تست سیستمهای AI/ML به ارزیابی Dataset/Model/Production نیاز دارد؛ مقاله حاضر روی الگوی عمومی آزمایش عدمقطعیت تمرکز میکند.
Test و Infrastructure
Shared fixture، Cleanup ناقص، Port/CPU contention، Order dependency، Assertion زودهنگام، Retry پنهان و Sandbox بیرونی میتوانند Flake بسازند، حتی اگر SUT قطعی باشد. منبع را پیش از هر اصلاح به SUT نسبت ندهید.
Nondeterministic System با Flaky Test فرق دارد
Flaky test در تعریف عملی، با کد و شرایط مرتبط یکسان هم Pass و هم Fail نشان میدهد. مقاله Google Testing Blog درباره منبع Flaky test تأکید میکند Nondeterminism ممکن است در Test، Framework، SUT، Dependency، OS، Hardware یا Network باشد؛ اگر ریشه در Production code داشته باشد، نادیدهگرفتن Test یعنی نادیدهگرفتن Product bug.
- Permitted variability: چند خروجی همگی داخل Contract هستند؛ Test نباید Exact equality بخواهد.
- Product flake: یک مسیر گاهی Invariant را میشکند؛ Fail واقعی است.
- Test flake: Assertion/Fixture/Wait نتیجه کاذب میسازد.
- Environment flake: آزمایش کامل نشده یا Dependency آماده نبوده است.
- Unknown: Evidence برای انتساب کافی نیست؛ Inconclusive.
تکنیکهای عمیق جمعآوری شواهد و بازسازی در راهنمای بازتولید باگ متناوب آمده است. این مقاله بر طراحی Trial و Verdict در سطح Suite تمرکز دارد.
Control، Randomize، Observe یا Block؟
| عامل | عمل ممکن | مثال |
|---|---|---|
| PRNG | Control + record + vary | Seed ثابت برای Replay، Seedهای متنوع برای Discovery |
| Clock | Virtualize/control | Fake clock و Event-driven advance |
| Scheduler | Systematically perturb | Stress/Jcstress/Deterministic simulator |
| Network | Inject/record | Delay/loss/reorder با Profile نسخهدار |
| External API | Stub + sandbox + bounded live | لایههای متفاوت Fidelity |
| Production traffic | Sample/stratify/observe | Slice و Trace correlation |
| Unknown variance | Block verdict | تا رفع Evidence gap، Inconclusive |
کنترل کامل برای Debugging عالی است اما Production realism را کم میکند. Randomization برای Discovery مفید است اما بدون Capture قابلبازتولید نیست. یک Strategy بالغ هر دو Lane را دارد: Controlled replay و Stochastic exploration.
قرارداد آزمایش سیستم غیرقطعی
- Question/Risk: دقیقاً چه ادعایی را میسنجیم؟
- Trial unit: یک Request، Session، Batch، Schedule یا Training run؟
- Population/workload: Trialها نماینده کدام Traffic/State هستند؟
- Controlled factors: Build، Data، Clock، Config و Dependency.
- Randomized factors: Seed، Delay، Schedule یا Input distribution.
- Strata: Channel، PSP، Amount band، Locale و Device.
- Outcome/Oracle: Event، Invariant، Rate، Quantile یا Distribution.
- Budget/effect: Threshold و کوچکترین تغییر مهم.
- Sample plan: تعداد Trial، Warm-up، Independence و Repeat.
- Uncertainty method: Confidence interval/Test و Assumptionها.
- Stop policy: Fixed sample یا Sequential rule ازپیشتعریفشده.
- Verdict: Pass، Fail و Inconclusive.
- Replay packet: چه اطلاعاتی مسیر Fail را بازسازی میکند؟
- Owner/expiry: چه کسی Budget و Distribution را تأیید میکند؟
عبارت «صد بار اجرا کن» Contract نیست. ممکن است Trialها Cache/State مشترک داشته باشند، Seed یکسان باشد یا فقط یک Slice آسان را تکرار کنند. صد مشاهده وابسته، معادل صد Sample مستقل نیست.
اول Oracle را متناسب با تنوع طراحی کنید
Exact Expected فقط یکی از گزینههاست. راهنمای Test Oracle منبع، Comparator، Tolerance و Uncertainty را تفکیک میکند. برای سیستم غیرقطعی میتوان از این Oracleها استفاده کرد:
- Set oracle: خروجی باید عضو مجموعه مجاز باشد؛
- Invariant: Balance، Authorization و Uniqueness همیشه برقرار بماند؛
- Relation/Metamorphic: تغییر ورودی باید رابطه مشخصی در خروجی بسازد؛
- Temporal: State مجاز در Window مشخص حاصل شود؛
- Statistical: Rate/Distribution/Quantile با Budget سازگار باشد؛
- Differential: اختلاف نسخهها Signal برای Triage باشد؛
- Human rubric: قضاوت کیفی با Rubric و Agreement سنجیده شود.
Pass یک Trial با Pass یک Distribution فرق دارد. همچنین Statistical Oracle جای Invariant قطعی را نمیگیرد.
سه Verdict بهجای Pass/Fail اجباری
| Verdict | قاعده نمونه برای نرخ ≤۱٪ | اقدام |
|---|---|---|
| Pass | کران بالای Interval ≤۱٪ | شاهد برای Scope تعریفشده |
| Fail | کران پایین Interval >۱٪ یا نقض Invariant | Block/Triage |
| Inconclusive | Interval از ۱٪ عبور میکند یا Evidence ناقص است | Sample/Design/Evidence بیشتر |
Inconclusive شکست تیم نیست؛ از تبدیل داده ناکافی به تصمیم قطعی جلوگیری میکند. Retry-until-green معمولاً همین حالت را به Pass کاذب میشوید.
Rate، Mean و Percentile را درست انتخاب کنید
- Bernoulli rate: Slow/Not slow، error/no error؛
- Continuous metric: Latency، score یا resource usage؛
- Quantile: p95/p99 برای Tail، با روش و حجم Sample؛
- Distribution: وقتی شکل کامل و Modeها مهماند؛
- Paired difference: همان Seed/Scenario روی Baseline و Candidate؛
- Slice metric: نرخ به تفکیک PSP/Channel/Amount، نه فقط Aggregate.
میانگین میتواند Tail failure را پنهان کند. Percentile نیز بدون Trial population، Window و تعداد Sample قابلتفسیر نیست. قبل از دیدن نتیجه، Metric، Threshold، Confidence و Stop rule را ثبت کنید تا با چند بار نگاهکردن تصادفی False decision نسازید.
Confidence Interval چه میگوید؟
برای نتیجه دودویی، نرخ مشاهدهشده فقط یک برآورد نقطهای است. بازه اطمینان دامنهای از مقدارهای سازگار با Sample و روش را نشان میدهد؛ احتمال موفقیت آینده یا تضمین Product نیست. صفحه NIST درباره بازه اطمینان نسبت دوجملهای توضیح میدهد Approximation ساده، بهویژه در Sample کوچک یا Failure کم، میتواند نامناسب باشد.
ابزار رسمی SciPy binomtest آزمون نسبت دوجملهای و محاسبه Confidence interval را ارائه میکند. روش، یکطرفه/دوطرفهبودن، Confidence level، Sample plan و Assumption استقلال باید در Report ثبت شوند؛ صرفاً یک p-value را به معنی «احتمال درستبودن محصول» تفسیر نکنید.
آزمایش اجرایی: یک Green run چرا کافی نیست؟
در یک شبیهسازی کنترلشده، Outcome این بود که Completion Checkout از دو ثانیه عبور کند یا نه. Budget ازپیشتعریفشده حداکثر ۱٪ و روش Verdict، بازه Wilson دوطرفه ۹۵٪ بود. Candidate در شبیهساز احتمال پایه ۰٫۵٪ و Baseline احتمال ۳٪ داشت. این احتمالها برای ساخت آزمایشاند و ادعای Production نیستند.
function runTrials({ trials, slowProbability, seed }) {
const random = mulberry32(seed);
let slow = 0;
for (let trial = 0; trial < trials; trial += 1) {
if (random() < slowProbability) slow += 1;
}
return { slow, trials, rate: slow / trials };
}
function verdict({ low, high }, budget) {
if (high <= budget) return "pass";
if (low > budget) return "fail";
return "inconclusive";
}
خروجی واقعی Node.js ۲۴.۱۸.۰
node=v24.18.0 seed=20260811 budget=1.0%
candidate_n1: slow=0/1 rate=0.000% ci95=[0.000%,79.345%] verdict=inconclusive
candidate_n20: slow=0/20 rate=0.000% ci95=[0.000%,16.113%] verdict=inconclusive
candidate_n2000: slow=12/2000 rate=0.600% ci95=[0.344%,1.046%] verdict=inconclusive
candidate_n5000: slow=24/5000 rate=0.480% ci95=[0.323%,0.713%] verdict=pass
baseline_n2000: slow=62/2000 rate=3.100% ci95=[2.426%,3.954%] verdict=fail
candidate_replay_identical=true
یک و ۲۰ Trial هر دو صفر Failure داشتند، اما Uncertainty آنقدر بزرگ بود که Pass مجاز نبود. حتی ۱۲/۲٬۰۰۰ با نرخ ۰٫۶٪ هنوز کران بالای ۱٫۰۴۶٪ داشت و Inconclusive بود. Sample تصمیمِ ازپیشتعریفشده ۵٬۰۰۰ Trial، نرخ ۰٫۴۸٪ و بازه ۰٫۳۲۳–۰٫۷۱۳٪ ساخت و Pass شد. Baseline نیز چون کران پایین ۲٫۴۲۶٪ بود Fail شد.
اعداد میانی برای آموزشاند؛ در آزمایش واقعی نباید بعد از هر نگاه دلخواه متوقف شوید. یا Fixed sample را پیشاپیش تعیین کنید یا Sequential design معتبر با Boundaryهای ازپیشتعریفشده داشته باشید.
Seed ابزار Replay است، نه پوشش
Seed یک مسیر Randomness را قابلبازتولید میکند، اما سه سوءبرداشت رایج دارد:
- یک Seed ثابت فقط یک مسیر را تکرار میکند و Discovery را کم میکند؛
- Seed بدون نسخه Generator/Algorithm/Data ممکن است در Build بعدی مسیر دیگری بسازد؛
- Seed PRNG، Scheduler، Clock، Network و سرویس بیرونی را خودکار کنترل نمیکند.
دو Lane بسازید: در PR از Corpus Seedهای Regression برای سرعت و Replay استفاده کنید؛ در Nightly/Discovery Seedهای تازه و ثبتشده اجرا کنید. هر Failure جدید باید Seed/Trace خود را به Corpus قابلمدیریت اضافه کند، نه اینکه تا ابد هزار Seed بدون Owner انباشته شود.
Deterministic Simulation چه چیزی حل میکند؟
FoundationDB Simulation and Testing نمونه مهمی از کنترل System-level Nondeterminism است: Cluster، Network، Disk، Failure و Time در یک Simulation تکریسمانی مدل میشوند تا اجرای Randomized با Seed قابل Replay شود. ارزش الگو در ابزار خاص نیست؛ در این است که منابع عدمقطعیت از مرزهای قابلکنترل عبور کنند.
اما Fidelity محدود است. مستند FoundationDB Client Testing صریح میگوید Determinism شبیهساز به Actor model تکریسمانی متکی است و برای بعضی جنبههای Multi-threaded مناسب نیست؛ تستهای Cluster/API واقعی مکمل آن هستند.
| Lane | مزیت | چیزی که ممکن است از دست برود |
|---|---|---|
| Pure unit + fake clock | سریع و دقیق | Integration/OS behavior |
| Deterministic simulation | Fault/Time/Network replay | Real scheduler/hardware |
| Stress on real runtime | Interleaving و resource realism | Replay کامل |
| Staging/sandbox | Integration fidelity | Production traffic/state |
| Production observation/canary | واقعیترین Distribution | کنترل، هزینه و ریسک |
Randomized Testing را به Corpus تبدیل کنید
تولید Input تصادفی زمانی مفید است که Constraint و Property روشن، Failure ذخیره و مثال کوچک شود. راهنمای Property-Based Testing مالک Generator، Shrinking و Regression corpus است. در اینجا Rule مکمل ساده است:
- Seed و Generator version را ثبت کنید؛
- Input concretized را نیز ذخیره کنید تا فقط به Replay الگوریتم وابسته نباشید؛
- Failure را Shrink و به تست متمرکز تبدیل کنید؛
- Seedهای تازه را از Seedهای Regression جدا گزارش کنید؛
- Coverage/Distribution تولید را بسنجید، نه فقط تعداد Run.
Concurrency را با تکرار ساده تست نکنید
اجرای هزارباره یک تست با همان Load ممکن است همان Schedule آسان را تکرار کند. برای Concurrency چند روش مکمل لازم است:
- Invariant و State transition دقیق؛
- Scheduler perturbation و Delay در نقاط کنترلشده؛
- Barrier برای همزمانکردن عملیات متعارض؛
- Stress روی تعداد Worker/CPU و Runtime مختلف؛
- Race detector/Thread sanitizer؛
- Model checking یا Stress harness برای Interleavingهای کوچک؛
- History capture و در صورت لزوم Linearizability check.
Go Data Race Detector هنگام اجرای مسیرها Raceهای حافظه را گزارش میکند، اما فقط Raceهایی را میبیند که Runtime واقعاً اجرا کرده است و سربار دارد. نبود Report اثبات نبود Race نیست.
در اکوسیستم Java، OpenJDK jcstress Harness آزمایشی برای سنجش صحت Concurrency در JVM، Library و Hardware است. ابزار را بر اساس زبان و Memory model انتخاب کنید؛ یک UI test تکراری جای Harness سطح پایین را نمیگیرد.
Race، Deadlock و Atomicity Oracleهای متفاوت دارند
| ریسک | Oracle/شاهد | دام رایج |
|---|---|---|
| Data race | Detector report + conflicting stack | پوشش مسیر ناکافی |
| Lost update | Balance/version invariant | فقط Response ۲۰۰ |
| Duplicate effect | Unique ledger/idempotency evidence | میانگین نرخ کم |
| Deadlock/livelock | Progress deadline + thread dump | Timeout بدون Dump |
| Ordering | History/sequence relation | Log timestamp ناسازگار |
| Visibility | Allowed/forbidden outcome set | فرض حافظه ترتیبی ساده |
سیستم توزیعشده را با Sleep قضاوت نکنید
Eventual consistency یعنی هر مقدار در هر زمان مجاز نیست. باید Stateهای میانی مجاز، Convergence condition، Deadline و شرایط Failure مشخص شوند. Poll باید State هدف/خطای قطعی را مشاهده کند و در Deadline با Evidence کامل متوقف شود؛ Sleep پنجثانیهای هم ممکن است بیجهت کند و هم زیر Load کوتاه باشد.
راهنمای تست سیستمهای توزیعشده مرز Consistency/Partition/Coordination را پوشش میدهد و راهنمای تست تابآوری روی Failure mode، Degradation و Recovery evidence متمرکز است. مقاله حاضر Trial design و عدمقطعیت مشترک آنها را مدیریت میکند.
Temporal Oracle نمونه
- در تمام لحظهها: جمع Ledger نباید خراب شود؛
- تا ۵ ثانیه: Read replica میتواند نسخه قبلی را بدهد؛
- پس از Event offset تأییدشده: Order باید Paid شود؛
- تا Deadline: Queue backlog باید زیر Budget برگردد؛
- در Timeout: Verdict میتواند Inconclusive باشد، اما Evidence حذف نمیشود.
زمان را یک Dependency واقعی بدانید
Clock باید Interface قابلکنترل داشته باشد. برای Duration از Monotonic clock و برای زمان کسبوکار از Instant/Timezone/Calendar version استفاده کنید. Test case باید Freeze/Advance/Jump را صریح کند.
- مرز پایان روز، ماه و سال؛
- UTC در Storage و Asia/Tehran در Presentation؛
- تاریخ جلالی در UI و Instant میلادی در Contract؛
- Clock skew میان Nodeها؛
- NTP correction و Wall-clock jump؛
- Timer cancellation و Deadline propagation؛
- Version پایگاه Timezone در Evidence.
قانون تقویم/Timezone ممکن است در طول عمر محصول تغییر کند؛ Test fixture باید نسخه داده زمانی را Pin کند و Product behavior جدید را جدا ارزیابی کند.
Dependency را در سه سطح تست کنید
- Deterministic stub: Success/Error/Delay دقیق برای Logic و Replay؛
- Fault-capable emulator/sandbox: Protocol، Callback، Retry و Variation؛
- Bounded live/canary: رفتار واقعی با Guardrail، داده مجاز و هزینه کنترلشده.
فقط Stub، Contract/Fidelity را از دست میدهد؛ فقط Sandbox، تست را به Availability بیرونی وابسته میکند. نتیجه Environment failure را با Product failure یکی نکنید و Attemptهای Dependency را همراه Correlation ID ثبت کنید.
Model-Based Testing برای Stateهای مجاز
اگر چند State/Transition مجاز وجود دارد، Model بهجای یک Expected واحد مجموعه رفتارهای مجاز را تعریف میکند. راهنمای Model-Based Testing درباره State machine، Guard، Transition و Coverage جزئیات دارد.
برای Nondeterministic model سه چیز را ثبت کنید: Transitionهای مجاز، Fairness/Progress expectation و Probabilistic claim در صورت وجود. اینکه Model یک Transition را مجاز میداند، به معنی فراوانی درست یا نبود Starvation نیست.
Retry نتیجه اول را پاک نمیکند
Playwright Test Retries اجرای Pass در بار اول، Fail سپس Pass و Fail همه Attemptها را بهترتیب Passed، Flaky و Failed دستهبندی میکند. این تفکیک مفید است؛ اما Pipeline نباید Flaky را در عدد Pass حل کند.
| Attemptها | طبقهبندی | اقدام |
|---|---|---|
| Pass | Passed | Evidence عادی |
| Fail→Pass | Flaky signal | اولین Failure حفظ، Triage/Owner/SLA |
| Fail→Fail | Persistent failure | Block طبق Risk |
| Infra error→Pass | Environment instability | جدا از Product rate |
| Timeout بدون Evidence | Inconclusive | Instrumentation fix |
Retry میتواند برای جمعآوری Signal و کاهش تأخیر عملیاتی باشد، اما نباید Gate را بدون Policy تغییر دهد. سقف Attempt، دلیل Retry، State reset و Report هر Attempt را ثبت کنید.
Quarantine باید بازه زمانی داشته باشد
Quarantine موقت جلوی Block دائمی تیم را میگیرد، ولی اگر Owner/Expiry/Impact نداشته باشد قبرستان Test میشود. مستند رسمی Assertionهای Playwright توضیح میدهد برای UI غیرهمزمان، Assertionهای Auto-retrying مناسبترند و مقایسه فوری میتواند Flaky شود. راهحل عمومی انتظار برای State قابلمشاهده است، نه Sleep دلخواه.
- Bug ID، Owner و علت موقت؛
- آخرین Pass/Fail و نرخ Attempt؛
- Risk از دسترفتن Gate؛
- مسیر اجرای جدا و Alert؛
- Expiry و Escalation؛
- شرط بازگشت به Suite اصلی.
Reproduction Packet چه چیزهایی دارد؟
- Build/commit، dependency و schema versions؛
- Test/model/generator/runner version؛
- Seed و Concrete input؛
- Initial state، DB snapshot ID و Feature flags؛
- Wall/monotonic time و Timezone data version؛
- Scheduler/thread/process/CPU topology در صورت نیاز؛
- Network/fault profile و Dependency responses؛
- Request/idempotency/correlation/trace IDs؛
- Event history با Sequence number، نه فقط Timestamp؛
- Raw observations، Oracle version و Verdict؛
- Resource/GC/queue state؛
- تمام Attemptها، نه فقط Attempt سبز.
W3C Trace Context قالب استاندارد traceparent/tracestate را برای Correlation میان سرویسها تعریف میکند. Trace ID جای Seed، Business ID یا Log کامل را نمیگیرد؛ همچنین نباید PII را در Headerهای Trace قرار داد.
مطالعه عملی: Checkout و PSP ایرانی
Scope را به Checkout محدود کنید، نه «کل سیستم غیرقطعی». Contract نمونه:
- Canonical amount به IRR؛ تومان فقط نمایش؛
- Digits در Input: Latin/Persian/Arabic با Normalization نسخهدار؛
- PSP profile: Success، Decline، Timeout-before-commit، Timeout-after-commit؛
- Callback: single، duplicate، delayed، out-of-order؛
- Retry key: same/new و Deadline مشخص؛
- Invariant: حداکثر یک Charge و یک Ledger effect؛
- Temporal: Order/Reconciliation تا Window مشخص به State نهایی برسد؛
- Statistical: سهم Completion بالای ۲ ثانیه در Workload کنترلشده ≤۱٪؛
- Evidence: trace، idempotency key، PSP reference، ledger version، event offsets.
Duplicate charge با اولین مشاهده Fail است؛ آن را با Interval نرم نکنید. Budget یکدرصدی فقط برای Latency trial تعریف شده بود. Aggregate را نیز به PSP/Channel/Amount/Digits Slice کنید تا نسخه خوب در Web، شکست Android فارسی را پنهان نکند.
ماتریس تست عدمقطعیت
| منبع | کنترل/تنوع | Oracle | Evidence |
|---|---|---|---|
| Random jitter | Seed corpus + fresh seeds | Rate/quantile | seed + trial manifest |
| Callback order | Sequence generator | State/invariant | event offsets |
| Concurrent retry | Barrier + stress | unique effect | thread/ledger history |
| Replica lag | delay profile | temporal convergence | read/write version |
| Clock | fake/jump/skew | deadline/calendar rule | clock/tz version |
| PSP sandbox | stub+sandbox | contract + final state | raw request/callback |
| Test harness | isolated repeat/order shuffle | framework health | fixture/resource logs |
CI/CD را بر اساس هدف Trial لایهبندی کنید
| Lane | کنترل | هدف |
|---|---|---|
| PR deterministic | Fake clock/stub/regression seeds | Feedback سریع و Replay |
| PR flake detection | Repeat/order/worker variation محدود | Test health |
| Nightly stochastic | Fresh seed/schedule/network profiles | Discovery |
| Nightly statistical | Fixed sample و slice | Budget verdict |
| Release | Sandbox/real runtime/target matrix | Fidelity و Risk gate |
| Production canary | Guardrail/rollback/observation | Distribution واقعی |
Artifactهای هر Lane باید جدا باشند. راهنمای تست خودکار در GitLab CI الگوی Pipeline، Artifact و Debugging را پوشش میدهد؛ اینجا باید Seed، Trial manifest، Attempt و Statistical summary نیز افزوده شود.
ابزار را به سؤال نگاشت کنید
| سؤال | ابزار/روش | مرز |
|---|---|---|
| آیا PRNG path Replay میشود؟ | Seeded generator + concrete corpus | Scheduler/Dependency را کنترل نمیکند |
| Race حافظه رخ داد؟ | Race detector/TSan | فقط مسیر اجراشده |
| Outcomeهای JVM مجازند؟ | jcstress | Domain workflow نیست |
| System fault path Replay میشود؟ | Deterministic simulation | Fidelity runtime واقعی |
| State machine معتبر است؟ | MBT/model checking | مدل ممکن است ناقص باشد |
| Rate با Budget سازگار است؟ | Statistical package/notebook | Assumption و Sample plan |
| Failure کجا رخ داد؟ | Trace/log/history | Sampling و PII |
| Test UI Flaky است؟ | Trace/repeat/order/workers | Retry درمان نیست |
متریکهای سالم
- First-attempt pass/fail؛
- Fail→Pass rate و Attempt distribution؛
- Product/Test/Infra/Dependency/Unknown flake rate؛
- Time to reproduce و Reproduction packet completeness؛
- Unique seeds/schedules/profiles executed؛
- Invariant violations، حتی اگر Retry سبز شود؛
- Statistical interval width و Inconclusive rate؛
- Budget breach به تفکیک Slice؛
- Quarantine age، owner و escaped risk؛
- Unknown→classified time؛
- False-fail/false-pass ناشی از Oracle؛
- Cost per useful verdict.
Flake rate را فقط برای Testهایی حساب نکنید که Retry فعال دارند؛ این کار Testهای بدون Retry را از دید خارج میکند. همچنین First failure را پس از Pass دوم حذف نکنید.
ضدالگوهای تست سیستم غیرقطعی
- Retry until green: اولین Signal پاک میشود.
- صد بار اجرا کن: بدون Population، Independence و Verdict.
- Seed ثابت در همه Laneها: Discovery متوقف میشود.
- Seed برابر Replay کامل: Schedule/Clock/Dependency جا میماند.
- Sleep دلخواه: State synchronization نیست.
- Average-only: Tail و Slice failure پنهان میشود.
- p-value برابر احتمال صحت: تفسیر آماری غلط.
- صفر Failure برابر نرخ صفر: Uncertainty نادیده گرفته میشود.
- Statistical budget برای Integrity: نقض قطعی نرم میشود.
- Quarantine بدون Expiry: Coverage دائماً کم میشود.
- همه Flakeها Test bug هستند: Product race از دست میرود.
- همه Flakeها Product bug هستند: Harness/Infra هدررفت میسازد.
- Trace sampling کور: Rare failure بیشاهد میماند.
- Exact Expected برای خروجی مجاز متنوع: False fail.
- Production-only validation: کنترل و ایمنی حذف میشود.
برنامه ۳۰روزه
هفته اول: Taxonomy و Baseline
- ۲۰ Flake/Incident اخیر را Product/Test/Infra/Dependency/Unknown طبقهبندی کنید؛
- یک Critical flow و Invariantهای آن را مشخص کنید؛
- اولین Reproduction packet schema را بسازید؛
- First-attempt و Retry result را جدا ذخیره کنید.
هفته دوم: Control و Replay
- Clock/Random/Dependency را Interface کنید؛
- Regression seed corpus و fresh-seed lane بسازید؛
- State-based wait را جای Sleep بگذارید؛
- Trace/Correlation را End-to-end کنید.
هفته سوم: Trial و Verdict
- یک Outcome احتمالاتی مجاز با Budget واقعی انتخاب کنید؛
- Population، Sample، Interval و Stop rule را پیشنویس کنید؛
- Pass/Fail/Inconclusive را در Report اجرا کنید؛
- Sliceها و Independence را Review کنید.
هفته چهارم: Concurrency و Governance
- Race/stress/schedule lane متناسب با Stack اضافه کنید؛
- Quarantine owner/expiry/SLA را اعمال کنید؛
- یک Failure را Replay، Shrink و Regression کنید؛
- متریکها و Residual risk را با Product/SRE مرور کنید.
چکلیست نهایی
- □ Nondeterminism مجاز، Product defect و Test flake جدا شدهاند.
- □ Trial unit و Population هدف روشناند.
- □ Controlled/Randomized/Observed factors ثبت شدهاند.
- □ Seed همراه Concrete input و نسخهها ذخیره میشود.
- □ Invariant قطعی با Budget آماری نرم نشده است.
- □ Oracle نوع Set/Property/Temporal/Statistical مناسب دارد.
- □ Sample plan و Stop rule پیش از نتیجه تعیین شدهاند.
- □ Interval/Assumption/Confidence level گزارش میشود.
- □ Pass، Fail و Inconclusive تعریف شدهاند.
- □ First failure پس از Retry حفظ میشود.
- □ Sleep با State-based synchronization جایگزین شده است.
- □ Concurrency با ابزار/Stress مناسب Runtime تست میشود.
- □ Eventual consistency Window و Deadline دارد.
- □ Trace/Business/Idempotency/Event IDs Correlate هستند.
- □ Dependency stub/sandbox/live نقش جدا دارند.
- □ Sliceهای ایرانی پول/رقم/PSP/زمان بررسی شدهاند.
- □ Quarantine Owner، Expiry و Escalation دارد.
- □ Controlled replay و Stochastic discovery هر دو اجرا میشوند.
سؤالات متداول تست سیستمهای غیرقطعی
آیا هر سیستم با خروجی متفاوت، غیرقطعی است؟
نه لزوماً. ممکن است Input، State، Clock، Dependency یا نسخه داده ثبت نشده باشد. ابتدا شرایط مرتبط را کامل کنید؛ سپس مشخص کنید چند خروجی طبق Contract مجازند یا یک Defect/Flake وجود دارد.
چند بار باید یک تست غیرقطعی را اجرا کنیم؟
عدد جهانی وجود ندارد. به Outcome، Budget، نرخ موردانتظار، Effect مهم، Confidence، Independence و هزینه بستگی دارد. Sample size و Stop rule را پیش از دیدن نتیجه با کمک متخصص آمار یا روش معتبر تعیین کنید.
آیا Seed مشکل تکرارپذیری را حل میکند؟
فقط بخشی را. Seed مسیر PRNG را با نسخه و ترتیب فراخوانی ثابت Replay میکند؛ Schedule، Clock، Network، Hardware، Dependency و Data drift ممکن است همچنان متفاوت باشند. Concrete input و Reproduction packet نیز لازماند.
آیا Retry کردن تست Flaky مجاز است؟
برای جمعآوری Signal و کاهش اختلال عملیاتی میتواند مجاز باشد، اما Fail→Pass باید Flaky بماند، اولین Failure حفظ شود و Policy Gate/Owner/SLA داشته باشد. Retry نباید نتیجه اول را به Pass ساده تبدیل کند.
فرق Pass و Inconclusive در تست آماری چیست؟
Pass یعنی شواهد طبق روش ازپیشتعریفشده با Budget سازگار است؛ Inconclusive یعنی Sample/Uncertainty/Evidence هنوز اجازه تصمیم نمیدهد. در مثال، ۰/۲۰ سبز بود اما کران بالای ۱۶٫۱۱۳٪ داشت و نامشخص ماند.
جمعبندی
سیستم غیرقطعی به تست بیقاعده نیاز ندارد؛ به قرارداد آزمایش دقیقتر نیاز دارد. منبع Variation را طبقهبندی کنید، عوامل مناسب را کنترل یا Randomize کنید، Invariant را از Budget احتمالاتی جدا نگه دارید، Trial و Population را تعریف کنید و هر Attempt را با Seed/State/Trace قابلممیزی سازید.
اجرای واقعی نشان داد یک یا بیست Trial بدون Failure نمیتواند Budget یکدرصدی را Pass کند. حتی ۲٬۰۰۰ Trial Candidate هنوز Inconclusive بود؛ ۵٬۰۰۰ Trial ازپیشتعریفشده Interval را زیر Budget برد، درحالیکه Baseline با کران پایین بالاتر از Budget Fail شد. نتیجه عملی روشن است: در Nondeterministic testing، سبزی یک Run داده است؛ Verdict محصول یک استنتاج نسخهدار، محدود و قابلبازتولید است.

