دو 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

۱۳ فیلد غیرقابل حذف

  1. Decision و مخاطب؛
  2. Test basis و Version؛
  3. Coverage item؛
  4. Scope و Granularity؛
  5. Denominator و Applicable rules؛
  6. Covered/Evidence state؛
  7. Exclusion و Unknown policy؛
  8. Tool/Compiler/Runtime version؛
  9. Build/Commit/Environment/Data identity؛
  10. Target/Gate و Hard constraint؛
  11. Exception/Risk acceptance و Expiry؛
  12. Owner و Reviewer؛
  13. 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

  1. amount < 0 به amount <= 0؛
  2. سقف عادی ۲۰ به ۲۵؛
  3. سقف VIP ۳۰ به ۲۰؛
  4. Math.floor به Math.ceil؛
  5. مرز fee از < به <=؛
  6. هزینه ارسال ۳۰٬۰۰۰ به ۳٬۰۰۰ ریال.

خروجی اجرای قطعی

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

  1. Baseline report را بدون Gate تنبیهی بسازید و Limitها را ثبت کنید.
  2. Componentهای Critical/volatile/long-lived/incident-prone را اولویت دهید.
  3. قبل از Refactor، Characterization test و Production invariant بسازید.
  4. New/changed code را با Gate متناسب نگه دارید.
  5. هر Sprint یک Blind spot پرریسک Legacy را درمان کنید.
  6. 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

  1. Test Coverage=Code Coverage: Requirement/Risk/Behavior/Data پنهان می‌شوند.
  2. عدد ۸۰–۹۰ برای همه: Context و Criticality با Default جایگزین می‌شود.
  3. سبزکردن Line بدون Assertion: Reachability با Correctness اشتباه می‌شود.
  4. قرمز=Test جدید: Dead code، Testability، مدل یا Risk acceptance دیده نمی‌شود.
  5. Aggregate کل سازمان: Component پرریسک پشت کد ساده پنهان می‌شود.
  6. رتبه‌بندی افراد/تیم‌ها: Copy/paste، Exclusion و تقسیم Code تشویق می‌شود.
  7. Report ناقص=عدد معتبر: Shard/Process گمشده به Pass کاذب می‌رسد.
  8. Denominator نامرئی: Tool/Compiler/Scope تغییر می‌کند و Trend جعلی می‌شود.
  9. Exclusion بدون Expiry: Blind spot دائمی و بی‌مالک می‌ماند.
  10. Diff-only: اثر Config/Schema/Dependency روی Code قدیمی دیده نمی‌شود.
  11. ۱۰۰٪=بدون باگ: Oracle، Requirement omission و Integration gap نادیده می‌ماند.
  12. Mutation score=کیفیت محصول: Fault model منتخب بیش از معنایش تعبیر می‌شود.
  13. Manual tests نامرئی: Evidence رفتاری فقط چون Code instrumentation ندارد حذف می‌شود.
  14. Production usage=Test passed: نبود Detection با Correctness اشتباه می‌شود.
  15. میانگین سادهٔ درصدها: 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 مانده‌اند.

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