دو Test Suite برای تابع محاسبهٔ مبلغ پرداخت داریم. هر دو دقیقاً همان شش ورودی را اجرا میکنند و ابزار برای هر دو ۱۰۰٪ Line، ۱۰۰٪ Branch و ۱۰۰٪ Function coverage گزارش میدهد. اما وقتی شش خطای ساختگی مانند تغییر سقف تخفیف، مرز هزینهٔ ارسال و گردکردن مبلغ وارد میکنیم، Suite اول فقط یک خطا را میبیند و Suite دوم هر شش را. پس عدد Coverage درست است، اما به سؤال محدودی پاسخ داده: «چه ساختاری اجرا شد؟» نه «آیا رفتار درست با Oracle حساس آزموده شد؟»
پوشش تست نرمافزار (Test Coverage) یک درصد واحد نیست؛ رابطهای شفاف میان Test basis، Coverage item، Denominator، وضعیت Evidence و تصمیم است. Code Coverage فقط یکی از نماهای آن است. در این راهنما، Requirement/Risk/Behavior/Code/Configuration/Data/Production coverage را از هم جدا میکنیم، Statement/Branch/Condition/MC/DC را دقیق میسنجیم، Reportهای ناقص و Denominator drift را میگیریم و یک Quality Gate ضدبازی برای PR و Release میسازیم.
پوشش تست چیست؟
Coverage اندازه یا توصیفی از میزان Exercise شدن مجموعهای از Coverage itemها توسط Testهاست. Item میتواند Requirement، Risk، Equivalence partition، Boundary، Decision-table rule، State transition، کد Statement، Branch، Condition، Configuration یا Journey باشد. بنابراین عبارت «Coverage ما ۸۲٪ است» تا زمانی که Item و Denominator معلوم نشدهاند، معنای تصمیمی ندارد.
فرمول پایه
coverage_ratio = covered_applicable_items / all_applicable_items
اما فقط وقتی معتبر است که:
تعریف item ثابت باشد؛
applicable/excluded/unknown جدا باشند؛
covered state دقیق تعریف شود؛
Run و Evidence معتبر و کامل باشند.
«Covered» برای Line یعنی حداقل یک Instruction متناظر اجرا شده؛ برای Requirement ممکن است فقط Test طراحیشده، Test اجراشده، یا Evidence معتبر Pass/Fail باشد. این Stateها را نباید یکی فرض کرد.
Code Coverage زیرمجموعهٔ Test Coverage است
Code Coverage ساختار پیادهسازیِ اجراشده را میسنجد. Test Coverage مفهوم وسیعتری است و میتواند Test basis، رفتار، ریسک، داده و محیط را هم مدل کند. تست دستی یا Black-box شاید Requirement مهمی را پوشش دهد بدون آنکه ابزار Code Coverage در همان Run فعال باشد؛ برعکس، Unit test ممکن است هر Line را اجرا کند ولی یک قرارداد کسبوکاری یا Integration را هرگز نسنجد.
Coverage با Quality، Effectiveness و Confidence برابر نیست
- Coverage: چه Itemهایی Exercise/Evidence شدهاند؟
- Oracle strength: آیا خروجی غلط واقعاً Fail میشود؟
- Defect-detection sensitivity: Suite به Failureهای مرتبط چقدر حساس است؟
- Fidelity: Test double/محیط/داده چقدر Failure mode واقعی را بازنمایی میکند؟
- Reliability: Evidence چقدر تکرارپذیر و بدون Flake/Infra noise است؟
- Adequacy: آیا مجموع Evidence برای تصمیم و Risk فعلی کافی است؟
برای طراحی Expected result و جلوگیری از False Pass، راهنمای Test Oracle مکمل ضروری Coverage است.
پرسش قبل از درصد: این Coverage برای کدام تصمیم است؟
Coverage باید به یک سؤال تصمیمی متصل شود. Dashboard عمومی «بیشتر بهتر است» تیم را به تولید Testهای کمارزش و Exclusionهای پنهان سوق میدهد.
نمونه سؤالهای معتبر
- آیا تمام Acceptance ruleهای Story تغییرکرده، Evidence معتبر دارند؟
- آیا همهٔ Riskهای Critical این Release دستکم یک Evidence مستقل و تازه دارند؟
- آیا Branchهای جدید Payment state machine اجرا شدهاند؟
- آیا تمام Transitionهای معتبر و نامعتبر سفارش آزموده شدهاند؟
- آیا Configurationهای پشتیبانیشدهٔ مرورگر/Locale/Payment method در Matrix حاضرند؟
- کدام Mutantهای روی منطق مالی با وجود Execution زنده ماندهاند؟
- آیا Observability برای Invariantهای باقیمانده پس از Release Evidence میدهد؟
پرسشهای نامعتبر
- چگونه عدد را تا جمعه به ۹۰٪ برسانیم؟
- کدام تیم Coverage بیشتری دارد؟
- آیا چون Quality Gate سبز است Release بدون ریسک است؟
- چند Test باید اضافه کنیم تا «کیفیت تضمین» شود؟
قرارداد پوشش تست بسازید
یک Coverage Contract تعریف را از Tool و سلیقهٔ روز جدا میکند. بدون آن، تغییر نسخهٔ Compiler، Exclusion یا Scope میتواند درصد را عوض کند بدون اینکه Evidence بهتر شده باشد.
قالب Coverage Contract
coverage_contract_id: checkout-release-v12
decision: release readiness for checkout build 2841
basis:
requirements: checkout-spec@sha256:...
risks: checkout-risk-register@v17
code: commit a4c91e7
configurations: supported-matrix@2026-Q3
coverage_dimensions:
- item: critical_risk
denominator: applicable critical risks for this release
covered_state: valid current evidence with declared oracle
gate: 100%; no unknown mapping
- item: changed_branch
denominator: instrumentable branches changed vs target commit
covered_state: executed by a valid test attempt
gate: contextual threshold + reviewed misses
- item: payment_state_transition
denominator: valid and selected invalid transitions in model v8
covered_state: executed with state and side-effect oracle
gate: all critical transitions
exclusions:
policy: source + rationale + owner + expiry + approval
evidence_identity:
tool_versions: { node: 24.18.0, coverage: built-in-v8 }
build: checkout-2841
environment: ci-linux-x64-v6
suite_manifest: sha256:...
report_manifest: sha256:...
validity:
expected_tests_equal_observed: true
complete_report_required: true
owner: checkout-team
risk_acceptor: payment-product-owner
review_on: 2026-09-30
۱۳ فیلد غیرقابل حذف
- Decision و مخاطب؛
- Test basis و Version؛
- Coverage item؛
- Scope و Granularity؛
- Denominator و Applicable rules؛
- Covered/Evidence state؛
- Exclusion و Unknown policy؛
- Tool/Compiler/Runtime version؛
- Build/Commit/Environment/Data identity؛
- Target/Gate و Hard constraint؛
- Exception/Risk acceptance و Expiry؛
- Owner و Reviewer؛
- Review trigger و تاریخ.
شش بُعد مکمل Coverage
Dashboard سالم چند درصد نامرتبط را جمع نمیکند؛ هر بُعد را برای پرسش خودش نگه میدارد.
۱. Requirement و Acceptance coverage
Coverage item میتواند Requirement، Acceptance criterion، Business rule یا NFR باشد. Trace سادهٔ Requirement→Test فقط میگوید رابطه ثبت شده؛ نه اینکه Test معتبر اجرا، Oracle صحیح و نتیجه تازه است.
REQ evidence state ∈ {
unmapped,
test-designed,
ready,
valid-passed,
valid-failed,
blocked,
invalid-run,
expired,
partial,
not-applicable-with-rationale
}
برای استخراج Requirementهای قابل آزمون و ساخت RTM، تحلیل نیازمندیها در STLC را ببینید.
۲. Risk coverage
Risk coverage میپرسد کدام Risk statementها با چه Depth، Boundary، Fidelity، Oracle و Freshness Evidence دارند. شمار Test یا Pass بهتنهایی معادل پوشش ریسک نیست.
risk_evidence_coverage =
Σ(weight_i × evidence_state_factor_i) / Σ(weight_i)
نمونه عاملها (صرفاً قراردادی، نه احتمال):
valid-current-complete = 1
partial = 0.5
expired/invalid/none = 0
وزنها حقیقت احتمالاتی نیستند و نباید Critical hard constraint را با میانگین جبران کنند. ممکن است Risk-weighted coverage برابر ۹۴٪ باشد اما یک Critical بدون Evidence، Release را Hold کند. مدل درست Risk در راهنمای تست مبتنی بر ریسک آمده است.
۳. Behavior/model coverage
بسته به مدل، Item میتواند Equivalence partition، Boundary value، Decision-table rule، State، Transition، Sequence، Pair/t-way interaction، Property یا User journey باشد. هر Technique Denominator خاص دارد؛ «۱۰۰٪ Transition» دربارهٔ Boundary یا Oracle چیزی نمیگوید.
- EP: هر Partition معتبر/نامعتبر Applicable؛
- BVA: Boundary و نقاط just-below/on/just-above طبق Technique؛
- Decision table: هر Rule feasible؛
- State model: State/valid transition/all transition یا N-switch؛
- CTD: تمام interactionهای معتبر در Strength تعریفشده؛
- Journey: گام، Role، حالت و Side effectهای Critical.
برای Denominator معتبرِ Pairwise/t-way و Constraintها، راهنمای طراحی تست ترکیبیاتی را به کار ببرید.
۴. Structural/Code coverage
Item از ساختار Implementation میآید: Instruction، Statement/Line، Function/Method، Branch/Decision، Condition یا MC/DC. این بُعد نقاط اجرانشده را آشکار میکند و برای بازبینی Change بسیار مفید است، اما Requirement، تعامل سرویس، داده، زمان یا Outcome را بهطور خودکار پوشش نمیدهد.
۵. Configuration، Platform و Data coverage
مرورگر، OS، Device، Locale، Direction، Feature flag، Payment provider، Schema version، Role، Tenant، Network mode و Data shape میتوانند Coverage item باشند. Cartesian product معمولاً بسیار بزرگ است؛ Strength و Constraint را Risk-based انتخاب کنید و Supported/Unsupported/Unknown را جدا نگه دارید.
۶. Operational/Production evidence
Canary، Synthetic، Real-user telemetry، Invariant alert، Reconciliation و Incident feedback میتوانند Riskهای باقیمانده را Monitor کنند. این Evidence جای Test پیش از Release را نمیگیرد و «کاربر آن مسیر را اجرا کرد» به معنی Correctness نیست. Production cohort، privacy، sampling، observability gap و detection delay باید معلوم باشد.
انواع Code Coverage و مرز هرکدام
برای آموزش عمیق White-box و طراحی Test از Control flow، راهنمای تست جعبه سفید مالک آن Intent است. اینجا انواع را از منظر Measurement contract و تفسیر مرور میکنیم.
Instruction coverage
کوچکترین Instructionهای قابل شمارش در سطح bytecode/machine-like representation را میسنجد. در JaCoCo، Counterها از Java class file و bytecode مشتق میشوند؛ بنابراین Source formatting و Synthetic code میتواند Line mapping را غافلگیرکننده کند. مستندات Coverage Counters در JaCoCo Instruction، Branch، Line، Method، Class و Complexity را دقیق تعریف میکند.
Statement و Line coverage
میپرسد چه Statement یا Source line اجرایی حداقل یک بار اجرا شده است. Line و Statement همیشه مترادف نیستند: یک Line میتواند چند Statement/Instruction یا یک Statement چندخطی داشته باشد. Formatting یا Transpilation نیز Denominator را تغییر میدهد.
line_coverage = covered_executable_lines / executable_lines
مثال:
720 covered / 900 executable = 80%
این عدد نمیگوید:
کدام ورودی اجرا شد؛
کدام Branch گرفته شد؛
چه Assertionی نتیجه را سنجید؛
کدام Requirement یا Risk پوشیده شد.
Function/Method coverage
تابع یا Method وقتی Covered محسوب میشود که حداقل یک بار Invoke شود. یک Invocation خوشحال میتواند Function coverage را سبز کند، حتی اگر Error handling، Boundary و Side effectها دستنخورده باشند.
Branch/Decision coverage
خروجیهای Decision مانند True/False یا Caseهای Switch را میسنجد. طبق ISTQB CTFL، Branch coverage نسبت Branchهای Exerciseشده است و ۱۰۰٪ Branch، Statement coverage را subsume میکند؛ با این حال نتیجهٔ ابزار به Instrumentation و تعریف Branch وابسته است.
Coverage.py Line و Branch را پشتیبانی میکند و در Branch mode انتقالهای ممکن Source→Destination یا Arcها را با انتقالهای دیدهشده مقایسه میکند. JaCoCo نیز صریحاً Exception handling را در Branch counter خود حساب نمیکند. پس «Branch ۸۰٪» میان دو Tool/Language الزاماً Comparable نیست.
Condition coverage
در تصمیم مرکب مانند A && B میپرسد هر Condition به True و False رسیده است یا نه. Condition coverage لزوماً نشان نمیدهد هر Condition مستقل از بقیه Outcome تصمیم را تغییر داده یا همهٔ ترکیبها دیده شدهاند. Short-circuit evaluation نیز اجرای Conditionها را محدود میکند.
MC/DC
Modified Condition/Decision Coverage علاوه بر Outcomeهای Decision و True/False شدن Conditionها، میخواهد اثر مستقل هر Condition بر Outcome نشان داده شود. این معیار در زمینههای Safety-critical کاربرد مقرراتی/Assurance خاص دارد، نه اینکه برای هر وباپ Target عمومی باشد.
برای مثال، راهنمای SWE-۲۱۹ ناسا صددرصد MC/DC را برای Componentهای Safety-critical در Scope خود الزام میکند و Waiver را با rationale/approval میخواهد. این مثال نشان میدهد Target از Criticality و Standard میآید؛ نمیتوان آن را به تمام نرمافزارها تعمیم داد.
Path coverage
همهٔ مسیرهای ممکن از Control-flow graph را هدف میگیرد. حلقه، Recursion، State و شرطهای متعدد باعث مسیرهای نامحدود یا انفجار ترکیبی میشوند؛ بعضی Pathها نیز Infeasible هستند. بنابراین «Path coverage جامعترین و همیشه مطلوبترین معیار» تعبیر مفیدی نیست. Scope، Path length، loop bound و feasible model باید صریح باشند.
ابزارها Coverage را یکسان تعریف نمیکنند
| ابزار/اکوسیستم | نمونه Item/محاسبه | نکتهٔ تفسیر |
|---|---|---|
| JaCoCo / JVM | Bytecode instruction، branch، line، method، class، complexity | Line به debug info و mapping؛ exception handling Branch نیست |
| coverage.py / Python | Line و Branch/arc | Source/omit/exclude/plugin/concurrency روی Scope اثر دارد |
| Node/V8 | Line، branch، function در گزارش built-in/c8-like | Transpile/source map/subprocess باید همسو باشد |
| Istanbul/Jest | Statement، branch، function، line | Instrumentation و source map باید Versioned باشد |
| Coverlet/.NET | Line، branch، method | Collector/format/filter/assembly scope را ثبت کنید |
| SonarQube | Coverage ترکیبی از covered lines/conditions | Aggregation و new-code definition با Tool خام فرق دارد |
در مستندات فعلی Metricهای SonarQube، Coverage از ترکیب covered lines و conditions محاسبه میشود و New coverage همان تعریف را روی کد جدید/بهروزشده محدود میکند. پس عدد Dashboard را بدون Formula و Source report با عدد JaCoCo مقایسه نکنید.
Coverage Manifest حداقلی
{
"run_id": "cov-2026-08-12-1551",
"commit": "a4c91e7",
"tested_artifact_digest": "sha256:...",
"tool": "node-v8-coverage",
"tool_version": "24.18.0",
"metric": ["line", "branch", "function"],
"scope_include": ["src/payment/**/*.mjs"],
"scope_exclude": ["generated/**", "vendor/**"],
"exclusion_policy_version": "cov-policy-v5",
"suite_manifest_hash": "sha256:...",
"expected_test_files": 42,
"observed_test_files": 42,
"shards_expected": 4,
"shards_merged": 4,
"report_complete": true
}
Denominator؛ جایی که درصدها دستکاری میشوند
Coverage میتواند بدون افزودن حتی یک Test بالا برود: Code از Scope حذف شود، Generated code بیشتر شود، File rename یا Compiler mapping عوض شود، Baseline New code حرکت کند یا Shard ناموفق از Merge جا بماند.
هر Exclusion باید قرارداد داشته باشد
exclusion = {
path_or_rule,
reason,
ownership,
risk/evidence_alternative,
approver,
created_at,
expires_at,
review_trigger
}
Generated/Vendor/defensive-unreachable/platform bootstrap ممکن است Exclusion مشروع باشند، اما «تستکردنش سخت است» دلیل کافی نیست. Dead code باید حذف شود؛ Unreachable ادعایی باید با Design/analysis اثبات شود؛ و Risk باید پذیرش یا Evidence جایگزین داشته باشد.
Denominator drift را گزارش کنید
- Executable line/branch count نسبت به Baseline؛
- Included/excluded file count و دلیل Delta؛
- Generated/synthetic code version؛
- New-code baseline/reference branch؛
- Tool/compiler/source-map تغییرکرده؛
- Requirements/Risks اضافه، حذف، Split یا N/A شده؛
- Configuration matrix و Support policy تغییرکرده.
گزارش ناقص باید Inconclusive باشد
اگر یک Shard، Process فرزند یا Browser project Report نداده، Merge نباید درصد موجود را Coverage کل بنامد. Expected test/report/artifact identity را با Observed مقایسه کنید. Missing evidence = Inconclusive، نه صفر خودکار و نه Pass.
Baseline، Overall و Diff/New-code Coverage
Legacy codebase ممکن است Overall coverage پایین و بدهی بزرگ داشته باشد. Threshold سراسری سخت میتواند تیم را به Testهای سطحی یا Exclusion وادارد. تمرکز روی Change مفید است، اما فقط وقتی Scope دقیق و Risk pathهای خارج از Diff هم دیده شوند.
Overall coverage
برای Trend بلندمدت و Blind spotهای موجود مفید است. با تغییر Denominator، Tool و Suite composition باید Break در سری زمانی ثبت شود. درصد Aggregate بدون Drill-down به Component/Risk، نواحی بحرانی را پشت کد ساده پنهان میکند.
Diff/New-code coverage
میپرسد Code اضافه یا تغییرکرده چقدر Exercise شده است. مستندات New Code در SonarQube ۲۰۲۶.۱ چند تعریف مانند Previous version، Days، Specific analysis و Reference branch دارد و در PR تفاوت با Target branch را New code میداند. این انتخاب باید در Manifest ثبت شود.
محدودیت Diff
- تغییر یک Config یا Schema میتواند کد قدیمی زیادی را تحت اثر قرار دهد.
- حذف Line شاید مسیر Error handling قدیمی را تغییر دهد اما Line جدیدی نسازد.
- Dependency update یا Feature flag، Change impact را خارج از Text diff میبرد.
- چند Line تازه با Branchهای متعدد و Input domain بزرگ، درصد ناپایدار میسازند.
- Test فقط Line تازه را Touch میکند ولی Behavior قدیمیِ متأثر را Assert نمیکند.
پس Diff coverage باید همراه Change-impact map، Risk/Requirement evidence و Reviewed misses باشد.
هیچ درصد ایدهآل عمومی ۸۰ یا ۹۰ وجود ندارد
عدد هدف باید از Criticality، Change rate، عمر Code، Complexity، Failure impact، Testability، Standards و Cost of evidence بیاید.
چرا عدد عمومی خطرناک است؟
- کد سادهٔ Getter میتواند Aggregate را بالا و Payment logic را پنهان کند.
- تیم پس از رسیدن به Target انگیزهٔ پوشش Risk مهم بعدی را از دست میدهد.
- Testهای بدون Oracle فقط برای Touch کردن Line نوشته میشوند.
- Code سختتست بهجای Testability improvement، Exclude میشود.
- Metric به رتبهبندی فرد/تیم تبدیل و Denominator بازی میشود.
راهنمای Code Coverage گوگل صریحاً عدد ایدهآل جهانی را رد میکند و Criticality، Change frequency، عمر و Complexity را مبنای تصمیم میداند؛ همچنین نسبت به Testهای copy/paste برای بالا بردن عدد هشدار میدهد. اعداد داخلی/مثالی یک سازمان Context همان سازماناند، نه Standard صنعت.
در چه شرایطی ۱۰۰٪ معنادار است؟
گاهی Standard یا Risk policy برای Scope دقیق، Criterion دقیق و Waiver کنترلشده ۱۰۰٪ میخواهد؛ مانند MC/DC در Component Safety-critical مشخص. حتی آنجا نیز ۱۰۰٪ Structural coverage اثبات Correctness یا نبود Requirement اشتباه نیست و با Review، traceability، independence و سایر Assurance activityها ترکیب میشود.
آزمایش قطعی: Coverage یکسان، قدرت Oracle متفاوت
تابع ساختگی زیر مبلغ قابل پرداخت را به ریال محاسبه میکند: مبلغ منفی Reject، سقف تخفیف VIP برابر ۳۰٪ و عادی ۲۰٪، گردکردن رو به پایین و برای مبلغ Discounted کمتر از ۵۰۰٬۰۰۰ ریال، ۳۰٬۰۰۰ ریال هزینهٔ ارسال.
کد تحت تست
export function payable(amount, discountPercent, vip) {
if (amount < 0) throw new RangeError('negative amount');
const cap = vip ? 30 : 20;
const discounted = Math.floor(
amount * (100 - Math.min(discountPercent, cap)) / 100
);
return discounted < 500_000 ? discounted + 30_000 : discounted;
}
همان شش ورودی برای هر دو Suite
| ورودی amount/pct/VIP | هدف ساختاری/رفتاری | Expected دقیق |
|---|---|---|
| -1 / 0 / false | شاخه مبلغ منفی | RangeError |
| 0 / 0 / false | مرز صفر و fee | 30,000 |
| 100,001 / 25 / false | سقف عادی و Floor | 110,000 |
| 100,001 / 35 / true | سقف VIP | 100,000 |
| 499,999 / 0 / false | زیر مرز fee | 529,999 |
| 500,000 / 0 / false | روی مرز عدم fee | 500,000 |
Suite ضعیف و قوی
// ضعیف: مسیرها را اجرا میکند، اما فقط Type/Finite را میسنجد.
for (const c of cases) {
if (c.throws) assert.throws(() => payable(...c.input));
else assert.ok(Number.isFinite(payable(...c.input)));
}
// قویتر: همان ورودیها، ولی Oracle قرارداد کسبوکار را میسنجد.
for (const c of cases) {
if (c.throws) assert.throws(() => payable(...c.input), RangeError);
else assert.equal(payable(...c.input), c.expected);
}
گزارش واقعی Structural coverage
Node.js 24.18.0 --test --experimental-test-coverage
Suite line branch functions
weak 100.00% 100.00% 100.00%
strong 100.00% 100.00% 100.00%
هر دو یک Test runner، فایل Production و ورودیهای یکسان دارند؛ پس Structural execution واقعاً برابر است.
شش Mutant
amount < 0بهamount <= 0؛- سقف عادی ۲۰ به ۲۵؛
- سقف VIP ۳۰ به ۲۰؛
Math.floorبهMath.ceil؛- مرز fee از
<به<=؛ - هزینه ارسال ۳۰٬۰۰۰ به ۳٬۰۰۰ ریال.
خروجی اجرای قطعی
| Suite | Line | Branch | Mutant killed | Mutation score در این Scope |
|---|---|---|---|---|
| Oracle ضعیف | ۱۰۰٪ | ۱۰۰٪ | ۱ از ۶ | ۱۶٫۷٪ |
| Oracle دقیق | ۱۰۰٪ | ۱۰۰٪ | ۶ از ۶ | ۱۰۰٪ |
Suite ضعیف فقط Mutant مرز صفر را میکشد، چون آن مورد به Exception تبدیل میشود؛ پنج خروجی غلط دیگر همچنان Number finite هستند. Suite قوی هر شش Contract change را Detect میکند.
محدودیت آزمایش
تابع، Testها و Mutantها ساختگی و انتخابشدهاند؛ Mutation score بالا در شش Mutant کوچک، Benchmark یا اثبات کیفیت محصول نیست. Mutantها همهٔ Failureها، Integration، Concurrent state، Security و Requirement omissions را بازنمایی نمیکنند. Demonstration فقط نشان میدهد Reachability یکسان میتواند با Reveal/Oracle sensitivity کاملاً متفاوت همراه باشد.
برای Statusهای Killed/Survived/No Coverage/Invalid، R-I-P-R و Equivalent mutant، راهنمای عملی Mutation Testing را ببینید. تعریف رسمی Mutant states و metrics در Stryker نیز Mutation score کل و score روی Covered code را جدا میکند.
خط قرمز Coverage report الزاماً Test backlog نیست
Uncovered item یک پرسش است، نه دستور خودکار «Test بنویس».
هشت Disposition برای Gap
- Add test: رفتار/Risk مهم و Oracle قابل اتکا دارد؛
- Move boundary: Test در سطح ارزانتر/وفادارتر بهتر است؛
- Refactor for testability: Coupling یا side effect مانع Evidence است؛
- Remove dead code: مسیر هیچ Contract معتبری ندارد؛
- Fix requirement/model: Denominator یا Expected behavior مبهم است؛
- Operational control: Test پیش از Release محدود است و Monitoring/Reconciliation لازم است؛
- Exclude with rationale: Generated/vendor/proven unreachable با Evidence جایگزین؛
- Accept/defer risk: مالک مجاز، دلیل، Expiry و Trigger بازبینی.
اولویت Gap
priority inputs:
failure impact and exposure
change frequency and code lifetime
uncertainty and incident history
structural complexity and coupling
missing behavior/requirement evidence
oracle feasibility and fidelity
cost and feedback delay
Hard constraints:
critical risk / regulatory item / safety invariant
نباید با میانگین یا کد کمریسک جبران شود.
Coverage را با Test Pyramid اشتباه نگیرید
Lineهای پوشیدهشده توسط Unit test نمیگویند Integration contract یا Journey کاربر Evidence دارد. E2E نیز ممکن است Lineهای زیادی را Touch کند اما Failure localization و Boundary depth ضعیفی داشته باشد.
Coverage attribution per layer
- Unit/Component: Branch/Condition/Property و Failure localization قوی؛
- Contract/API: Schema، authorization، state/business rule؛
- Integration: DB/queue/vendor semantics، idempotency و side effect؛
- System/E2E: Journey، wiring، deployment و user-visible behavior؛
- Exploratory/Human: ناشناختهها، UX، مدلهای پوشش پویا؛
- Production: cohort/invariant/reconciliation و residual risk.
یک Risk میتواند Evidence چندلایه بخواهد. نسبت ثابت جهانی برای لایهها وجود ندارد؛ هرم تست در عمل تصمیم Cross-layer portfolio را پوشش میدهد.
مثال ایرانی: Coverage قرارداد پرداخت
برای Checkout، «۹۰٪ Line coverage» بهتنهایی دربارهٔ دو برداشت، مبلغ، Callback و Reconciliation تصمیمی نمیدهد. Coverage map چندبعدی بسازید.
Risk/behavior items
| Risk/Invariant | Coverage item | Evidence لازم | وضعیت نمونه |
|---|---|---|---|
| دو برداشت | timeout-before/after-commit؛ retry identity | Stub + Sandbox + Ledger oracle | Partial؛ Sandbox after-commit محدود |
| دو اثر Callback | duplicate/out-of-order/late event | Inbox uniqueness + state/ledger assert | Valid passed |
| مبلغ غلط | IRR canonical، تومان display، fee/discount boundaries | Rule table + exact oracle + mutation sample | Valid passed |
| شناسه ناسازگار | فارسی/عربی/لاتین digits و Unicode | Normalization partitions + DB/API oracle | One partition missing |
| Cutoff اشتباه | UTC/Asia-Tehran/Jalali boundary | Injected clock + ledger business-date oracle | Expired after timezone-library update |
| عدم کشف | PSP↔Order↔Ledger mismatch | Reconciliation + alert drill | Implemented; effectiveness pending |
Structural items
- Branchهای State machine روی Pending/Unknown/Failed/Success؛
- Conditionهای retry: outcome، attempt budget، provider capability؛
- Error/exception pathهای Timeout، ۴۲۹، malformed callback؛
- Changed-code coverage برای Adapter و Migration؛
- Survivorهای Mutation در مبلغ/identity/state، نه تمام Codebase در هر PR.
Configuration/Data items
PSP × payment method × amount class × digit form × callback outcome × timezone را با Constraint و Mixed-strength مدل کنید. هر ترکیب Cartesian لازم نیست، اما هر Risk interaction باید Denominator و Strength مشخص داشته باشد. Sandbox unavailable را Covered ننامید؛ وضعیت Blocked/Alternative/Residual risk بدهید.
Coverage Dashboard ضدبازی
یک Gauge بزرگ ۸۷٪ تقریباً هیچچیز نمیگوید. Dashboard را به Decision و Drill-down وصل کنید.
لایه ۱: Validity
- Build/Commit/Environment/Suite identity؛
- Expected/Observed tests، shards و reports؛
- Pass/Fail/Blocked/Invalid/Inconclusive؛
- Tool/compiler/config/exclusion version؛
- Denominator delta و دلیل.
لایه ۲: Evidence coverage
- Critical Risk current-valid/partial/missing؛
- Requirement/Acceptance evidence state؛
- Model coverage به تفکیک Technique؛
- Configuration/Data gaps و Unknown؛
- Structural Overall/New code به Component؛
- Mutation/fault sensitivity در Scope منتخب.
لایه ۳: Health و Cost
- Flake/Retry/Invalid-run و Evidence age؛
- Feedback P50/P95 و Runner-minute؛
- Gap age، Exclusion age و Exception expiry؛
- Maintenance effort و Testability debt؛
- False Pass/Failهای شناختهشده.
لایه ۴: Outcome با محدودیت
- Escaped failure cluster و فرصت وقوع قابل مقایسه؛
- Production invariant violation و Detection delay؛
- Reopen/recurrence مرتبط با Gap؛
- Before/After همراه Cohort، Sample، Confounder و Guardrail.
برای تعریف Metric، Denominator و ضدبازی، ۱۲ متریک تصمیممحور تست نرمافزار را ببینید.
Quality Gate چندبعدی طراحی کنید
Gate خوب یک عدد Aggregate نیست؛ Hard constraint، Threshold زمینهای و Review انسانی برای Gap دارد.
نمونه Policy برای PR
PR coverage gate:
validity:
tested artifact == commit artifact
expected reports == observed reports
baseline tests valid
hard constraints:
changed critical acceptance/risk items: current evidence required
payment state-machine critical transitions: 100% applicable
no unapproved exclusion or unknown risk mapping
structural signals:
changed branch/line coverage: contextual project threshold
every material uncovered changed item: reviewed disposition
overall component coverage: no unexplained regression
sensitivity:
targeted diff mutation for critical business logic, if valid/affordable
survivors triaged; incomplete mutation run = inconclusive
exceptions:
authorized risk owner + rationale + alternative evidence + expiry
Gate نباید چه کند؟
- کد کمریسک را برای جبران Critical gap Average کند؛
- Report ناقص را صفر یا Pass قطعی تعبیر کند؛
- تغییر Tool/Denominator را Trend improvement نشان دهد؛
- همهٔ Repositoryها را با عدد یکسان بسنجد؛
- Coverage را تنها Release criterion بداند؛
- Failure محصول را با Coverage threshold پنهان کند.
SonarQube در Quality Gate پیشفرض فعلی خود برای New code عدد ۸۰٪ دارد، اما همان مستندات امکان Custom gate برای فناوری/پروژههای متفاوت را توضیح میدهد. Default ابزار یک تصمیم Context-specific شما یا استاندارد جهانی نیست.
Pipeline اندازهگیری معتبر
۱. Basis و Scope را Freeze کنید
Commit/Build، Requirement/Risk model، Include/Exclude، Runtime/Compiler، Test suite و Environment را قبل از Run ثبت کنید. Coverage روی Source A با Test artifact B بیاعتبار است.
۲. Instrumentation را Validate کنید
Source map، Subprocess، Worker، Dynamic code، Native extension، Test runner lifecycle و Shard collection را روی نمونهٔ کوچک بررسی کنید. یک Test کنترل بسازید که Line/Branch معلوم را اجرا و گزارش را Assert کند.
۳. Baseline Test run باید معتبر باشد
اگر Testها Fail/Timeout/Cancel شدهاند، Coverage جزئی ممکن است برای تشخیص مفید باشد اما نباید Gate جامع را Pass کند. Result semantics و report completeness مقدم بر درصد است.
۴. Reportها را loss-aware ادغام کنید
هر Shard/Project/Process شناسه داشته باشد. Merge باید Expected IDs، Duplicate، Stale report و Artifact hash را بررسی کند. Report مربوط به Commit قبلی نباید با Run فعلی ترکیب شود.
۵. چند بُعد را محاسبه کنید
Code report را با Traceability/Evidence map پیوند دهید، اما Percentها را در یک فرمول ساختگی مخلوط نکنید. Drill-down از Risk/Requirement به Test/Attempt/Oracle/Artifact و از Code gap به Owner/Disposition ممکن باشد.
۶. Gate و Decision record
خروجی میتواند Pass، Fail، Hold/Inconclusive یا Exception باشد. Exception باید تصمیمگیر مجاز، دلیل، Evidence جایگزین، Residual risk و Expiry داشته باشد.
بهبود Coverage بدون تورم Test Suite
Testهای موجود را قوی کنید
گاهی Input کافی است اما Oracle ضعیف؛ مانند آزمایش این مقاله. Assertion بر State، Side effect، Invariant، Audit و Error contract میتواند بدون افزودن Testهای بسیار، Sensitivity را بالا ببرد.
Test را به کوچکترین Boundary مسئول ببرید
Business rule را در Unit/Component، Contract را در API/consumer، Persistence/queue را در Integration و Critical journey را در E2E نگه دارید. یک E2E بزرگ که هزار Line را Touch میکند Coverage عددی بالا ولی diagnosis ضعیف میدهد.
Testability را اصلاح کنید
Clock، Random، Network، Storage و External provider را Injectable/observable کنید؛ State transition و Business invariant را قابل مشاهده سازید؛ Correlation ID و deterministic replay اضافه کنید. Hard-to-test code گاهی Signal طراحی است، نه دلیل Exclusion.
Gapهای همخانواده را با مدل پوشش دهید
بهجای Test موردی برای هر Line، Decision table، State model، Property یا CTD بسازید. Model باید Versioned، Constraint-aware و دارای Oracle باشد.
Dead code و Duplication را حذف کنید
حذف Code بیمصرف Denominator را پایین میآورد، اما این بازی نیست اگر با Evidence و Review ثابت شده هیچ Contract/Risk معتبری ندارد. حذف باعث کاهش Surface و نگهداری میشود؛ دلیل و Diff را شفاف گزارش کنید.
Coverage در Legacy codebase
هدفگذاری یکشبه روی Aggregate بالا معمولاً پرهزینه و بازیپذیر است. از Change و Risk شروع، اما Blind spotهای Legacy بحرانی را فراموش نکنید.
روش Characterization→Risk→Change
- Baseline report را بدون Gate تنبیهی بسازید و Limitها را ثبت کنید.
- Componentهای Critical/volatile/long-lived/incident-prone را اولویت دهید.
- قبل از Refactor، Characterization test و Production invariant بسازید.
- New/changed code را با Gate متناسب نگه دارید.
- هر Sprint یک Blind spot پرریسک Legacy را درمان کنید.
- Coverage gain را با Oracle/Mutation sample و Maintenance cost کنترل کنید.
Ratchet با کف ثابت فرق دارد
Ratchet میگوید Regression بیدلیل پذیرفته نیست و Gapها تدریجی بهبود یابند؛ Threshold ثابت میتواند پروژههای ناهمگون را یکسان کند. Ratchet نیز باید Denominator change و حذف Code را بفهمد و Exception داشته باشد.
Coverage برای Manual و Exploratory Testing
Manual test را نمیتوان فقط با Case count اندازه گرفت. Coverage item را از Model/Charter بیاورید: Feature area، Risk، Persona، Data class، State، Platform، Oracle یا Failure mode.
Session coverage
charter mission: explore refund callback
coverage model:
states: pending, unknown, paid, refunded
events: duplicate, late, out-of-order
data: rial/toman, Persian/Arabic/Latin digits
dependencies: PSP sandbox, ledger, notification
notes:
touched / deep / question / blocked / not explored
evidence:
observations, traces, data IDs, limitations, debrief decisions
Time spent یا تعداد Bug بهتنهایی Coverage نیست. عمق، Variation، Oracle و Unknownها را در Debrief ثبت کنید.
Before/After را چگونه گزارش کنیم؟
افزایش درصد فقط Outcome فرایندی است. گزارش باید نشان دهد چه Gapی بسته، چه Costی ایجاد و چه محدودیتی باقی مانده است.
Scope: checkout payment domain, commit range v11→v12
Tool/definition: Node 24.18 V8 line/branch; risk model v17
Sample: 42 PR runs before / 45 after
Change:
diff branch review + exact money oracles + targeted mutation on payment rules
Before:
changed branch coverage P50 = 71%
critical risk valid-evidence = 8/11
targeted mutants killed = 17/30
invalid coverage reports = 3/42
After:
changed branch coverage P50 = 86%
critical risk valid-evidence = 11/11
targeted mutants killed = 27/30
invalid coverage reports = 0/45
Cost/guardrails:
PR P95 +2.8 min; flake unchanged; 3 survivors accepted/queued with rationale
Limits:
mutant set and code changed; no causal claim about escaped defects;
PSP sandbox after-commit fidelity remains partial.
Percentile برای Coverage per PR همیشه لازم نیست، اما وقتی Diff size متفاوت است Distribution و Small-denominator effect را نشان دهید. Never average percentages بدون وزن Denominator: ۱/۱ و ۸۰/۱۰۰ را نباید میانگین سادهٔ ۹۰٪ نامید؛ Aggregate درست ۸۱/۱۰۱ است.
برنامهٔ ۳۰روزهٔ بهبود پوشش تست
روزهای ۱ تا ۵: تعریف
- Decisionها و مخاطبان PR/Release/Legacy را بنویسید.
- Coverage item/Denominator/State را برای Code، Requirement و Risk تعریف کنید.
- Tool/version/scope/exclusion را Inventory کنید.
- هیچ Target جدیدی تا Validity baseline اعمال نکنید.
روزهای ۶ تا ۱۰: اعتبار Pipeline
- Build identity و Source map/Instrumentation را Validate کنید.
- Expected/Observed Test/Shard/Report gate بسازید.
- Denominator/exclusion diff را Artifact کنید.
- Invalid/Inconclusive semantics را به Dashboard اضافه کنید.
روزهای ۱۱ تا ۱۵: Gap review
- Top Gapها را با Risk/Change/Complexity، نه Line count تنها، مرتب کنید.
- هر Gap را Add/Move/Refactor/Remove/Model/Monitor/Exclude/Accept کنید.
- Critical Riskهای بدون Evidence را Hard constraint قرار دهید.
روزهای ۱۶ تا ۲۰: Pilot Gate
- روی یک Component، Changed branch + Requirement/Risk evidence را Shadow کنید.
- Missهای مهم را در Code review نمایش دهید.
- Mutation sample را فقط روی Business logic پرریسک اجرا کنید.
- False block، Toil و PR latency را بسنجید.
روزهای ۲۱ تا ۲۵: Oracle و Testability
- Testهای high-coverage/low-sensitivity را با Survivorها پیدا کنید.
- Oracle را به State/side effect/invariant گسترش دهید.
- Clock/Network/Data dependencyهای سختتست را جدا کنید.
- Exclusionهای بیمالک یا منقضی را بازبینی کنید.
روزهای ۲۶ تا ۳۰: تصمیم و Scale
- Before/After، Cost و Guardrail را منتشر کنید.
- Threshold زمینهای و Hard constraintها را تصویب کنید.
- Exception/Risk acceptance با Expiry بسازید.
- تصمیم Adopt/Adapt/Continue/Stop و بازبینی ۶۰روزه ثبت کنید.
Anti-patternهای رایج Coverage
- Test Coverage=Code Coverage: Requirement/Risk/Behavior/Data پنهان میشوند.
- عدد ۸۰–۹۰ برای همه: Context و Criticality با Default جایگزین میشود.
- سبزکردن Line بدون Assertion: Reachability با Correctness اشتباه میشود.
- قرمز=Test جدید: Dead code، Testability، مدل یا Risk acceptance دیده نمیشود.
- Aggregate کل سازمان: Component پرریسک پشت کد ساده پنهان میشود.
- رتبهبندی افراد/تیمها: Copy/paste، Exclusion و تقسیم Code تشویق میشود.
- Report ناقص=عدد معتبر: Shard/Process گمشده به Pass کاذب میرسد.
- Denominator نامرئی: Tool/Compiler/Scope تغییر میکند و Trend جعلی میشود.
- Exclusion بدون Expiry: Blind spot دائمی و بیمالک میماند.
- Diff-only: اثر Config/Schema/Dependency روی Code قدیمی دیده نمیشود.
- ۱۰۰٪=بدون باگ: Oracle، Requirement omission و Integration gap نادیده میماند.
- Mutation score=کیفیت محصول: Fault model منتخب بیش از معنایش تعبیر میشود.
- Manual tests نامرئی: Evidence رفتاری فقط چون Code instrumentation ندارد حذف میشود.
- Production usage=Test passed: نبود Detection با Correctness اشتباه میشود.
- میانگین سادهٔ درصدها: Denominatorهای متفاوت نتیجهٔ غلط میسازند.
چکلیست پوشش تست قابل اعتماد
- آیا Decision و مخاطب Coverage روشن است؟
- آیا Test basis و Version ثبت شده است؟
- آیا Coverage item و Granularity دقیقاند؟
- آیا Numerator/Denominator/Applicable rules معلوماند؟
- آیا Covered state برای Requirement/Risk تعریف شده است؟
- آیا Pass/Fail/Blocked/Invalid/Expired/Partial جدا هستند؟
- آیا Tool/Runtime/Compiler/Source-map نسخهدارند؟
- آیا Tested artifact با Commit مورد گزارش یکی است؟
- آیا Expected/Observed tests، shards و reports برابرند؟
- آیا Report ناقص Inconclusive میشود؟
- آیا Exclusion دلیل، Owner، Alternative، Approver و Expiry دارد؟
- آیا Denominator drift کنار Trend دیده میشود؟
- آیا Overall و New/Diff جدا هستند؟
- آیا Diff با Change impact و Risk map تکمیل شده است؟
- آیا Critical gap با Aggregate جبران نمیشود؟
- آیا Oracle strength مستقل بررسی میشود؟
- آیا Mutation/fault sample در Scope مناسب و با Status کامل است؟
- آیا Gapها Disposition دارند، نه فقط رنگ قرمز؟
- آیا Layerهای Unit/API/Integration/E2E نقش مشخص دارند؟
- آیا Configuration/Data/Locale/Time coverage دیده میشود؟
- آیا Production evidence محدودیت Privacy/Detection دارد؟
- آیا Gate زمینهای، چندبعدی و دارای Exception expiry است؟
- آیا Cost، Flake و Feedback time Guardrail هستند؟
- آیا Coverage برای ارزیابی افراد استفاده نمیشود؟
پرسشهای متداول درباره پوشش تست
۱. تفاوت Test Coverage و Code Coverage چیست؟
Code Coverage نشان میدهد چه ساختارهایی از Implementation مانند Line/Branch/Condition اجرا شدهاند. Test Coverage گستردهتر است و میتواند Requirement، Risk، Behavior model، Configuration، Data و Journey را هم شامل شود. Code Coverage یک Evidence مهم اما ناکافی از Coverage کل است.
۲. درصد مناسب Code Coverage چند است؟
عدد جهانی وجود ندارد. Target را بر اساس Criticality، Standard، Change rate، Complexity، عمر Code، Testability و Cost تعیین کنید. برای Scopeهای Safety-critical ممکن است Criterion سخت مانند ۱۰۰٪ MC/DC لازم باشد؛ برای محصول معمولی، Target زمینهای همراه Reviewed gaps و Risk evidence مفیدتر از ۸۰/۹۰ عمومی است.
۳. آیا ۱۰۰٪ Branch coverage یعنی نرمافزار بدون باگ است؟
خیر. Branchها اجرا شدهاند، اما ورودیهای مرزی، ترکیب Conditionها، Requirement ناقص، Oracle ضعیف، State/Integration/Concurrency/Security و Environment gap ممکن است باقی بمانند. آزمایش این مقاله دو Suite با ۱۰۰٪ Branch و Mutation sensitivity بسیار متفاوت نشان میدهد.
۴. Diff coverage بهتر است یا Overall coverage؟
هر دو برای سؤال متفاوتاند. Diff/New-code feedback سریع روی تغییر میدهد؛ Overall Blind spot و Trend کد موجود را نشان میدهد. Diff باید با Change impact تکمیل شود و Overall با Component/Risk drill-down؛ هیچکدام جای دیگری را کامل نمیگیرد.
۵. چگونه Coverage را بدون بازیدادن Metric بهبود دهیم؟
Coverage contract و Denominator شفاف بسازید، Critical Risk را Hard constraint کنید، Missها را Review و Disposition دهید، Oracle/Testability را تقویت کنید، Report completeness و Exclusion expiry را Gate کنید و Coverage را برای رتبهبندی افراد به کار نبرید. Mutation sample و Outcome/Cost guardrail نیز Vanity را آشکار میکند.
جمعبندی
Coverage مفید از یک درصد شروع نمیشود؛ از سؤال تصمیمی و تعریف Coverage item آغاز میشود. Code Coverage برای کشف Structural blind spot عالی است، اما اجرای Line/Branch بهتنهایی دربارهٔ Requirement، Risk، Oracle، Fidelity، Configuration یا Correctness حکم نمیدهد. به همین دلیل، گزارش باید Basis، Denominator، State، Tool/version، Run identity و Validity را حمل کند.
سیستم بالغ، Overall و Diff را جدا میبیند، Critical gaps را پشت Average پنهان نمیکند، Mutation را Evidence مکمل میداند، Exclusion و Exception را منقضی میکند و Gap را به Test/Refactor/Model/Monitor/Remove/Accept مناسب میرساند. هدف «سبزترکردن Dashboard» نیست؛ هدف این است که دقیقاً بدانیم کدام پرسشها پاسخ معتبر دارند و کدام Riskها هنوز Unknown یا بدون Evidence ماندهاند.

