عدد «۱۰۰٪» روی داشبورد Checkout سبز است؛ ۶۲۴ واحد از ۶۲۴ واحد پوشش داده شدهاند و ۳۸۰ تست هم Pass شدهاند. مدیر میپرسد: «پس میتوانیم منتشر کنیم؟» پاسخ حرفهای نه «بله» است و نه «پوشش تست بیفایده است». ابتدا باید پرسید: ۶۲۴ چه چیزهاییاند، از کدام Build، با تعریف کدام ابزار، چه مواردی از مخرج حذف شدهاند و این عدد دقیقاً اجازهٔ چه ادعایی را میدهد؟
این راهنما «پوشش تست ۱۰۰٪» را از یک عدد نمایشی به یک Coverage Claim قابلممیزی تبدیل میکند. خروجی عملی آن قرارداد مخرج یا Denominator، رجیستری Universe، Evidence Manifest، قواعد Exclusion و Unknown و پروتکل Correction است. اگر دنبال آموزش تکنیکهای Statement و Branch هستید، ابتدا راهنمای تست جعبه سفید و پوشش کد را بخوانید؛ این مقاله مالکِ اعتبار خودِ ادعای درصد است.
خلاصه اجرایی: چه زمانی Coverage Claim معتبر است؟
- پوشش، نسبت «واحدهای واجد شرایطِ مشاهدهشده» به «کل واحدهای واجد شرایط در یک Universe نسخهدار» است؛ نه امتیاز کیفیت محصول.
- عبارت ۱۰۰٪ بدون نوع Coverage، واحد شمارش، Subject، Build، ابزار، تنظیمات، بازهٔ زمانی و سیاست Exclusion معنای قابلبازتولید ندارد.
- اجراشدن با Assert شدن، درستبودن Oracle، پوشش سناریو، نبود نقص، رضایت کاربر یا آمادگی Release یکی نیست.
- EXCLUDED، NOT_APPLICABLE، UNKNOWN، STALE و INVALID پنج حالت متفاوتاند؛ مخلوطکردن آنها مخرج را زیبا اما تصمیم را ضعیف میکند.
- هدف ۸۰٪، ۹۰٪ یا ۱۰۰٪ استاندارد عمومی نیست. Threshold باید به تصمیم، ریسک، هزینه و Guardrail متصل باشد.
- اگر Universe یا تعریف ابزار عوض شده باشد، مقایسهٔ دو درصد فقط پس از تصمیم Comparability معتبر است.
- یک Claim خوب علاوه بر عدد، محدودیت ادعا، Evidence، Unknown، Owner، Review Due و مسیر Correction دارد.
هدف جستوجو و مرز این راهنما
نیت اصلی کاربر اطلاعاتی و اجرایی است: «آیا پوشش تست ۱۰۰ درصد خوب است؟»، «درصد مناسب Code Coverage چقدر است؟»، «چرا پوشش ۱۰۰٪ تضمین کیفیت نیست؟» و «چطور عدد Coverage را درست محاسبه کنیم؟». پاسخ کوتاه این است که هیچ درصدی مستقل از قرارداد اندازهگیری تفسیر نمیشود.
برای طراحی یک سیستم چندبعدی شامل Requirement، Risk، Behavior، Configuration و Production Evidence، صفحهٔ پوشش تست نرمافزار از Code Coverage تا Risk Evidence مرجع اصلی است. برای تعریف KPI و جلوگیری از بازی متریک نیز راهنمای متریکهای تست نرمافزار را ببینید. این صفحه روی یک سؤال محدود اما سخت تمرکز دارد: آیا یک Coverage Claim مشخص، با مخرج و شواهد خودش معتبر و قابلتصحیح است؟
پوشش تست چیست؟ تعریف عملی، نه تعریف تزئینی
تعریف عملی زیر برای پوشش ساختاری کد، پوشش نیازمندی، ریسک، حالت، پیکربندی یا هر Universe شمارشپذیر قابلاستفاده است:
Coverage Claim = {
purpose, decision, subject, build,
coverageType, unit, coveredSemantics,
universeVersion, eligiblePredicate,
numerator, denominator, formula,
toolAndConfig, evidenceManifest,
exclusions, unknowns, freshness,
result, claimLimit, owner, correction
}
Coverage % = 100 × Covered eligible unique units
─────────────────────────────
All eligible unique units
کلمات «eligible»، «unique» و «covered» عمدیاند. اگر واحدی دوبار شمرده شود، اگر مورد خارج از Scope وارد صورت شود، اگر Unknown از مخرج ناپدید شود یا اگر صرفاً یک خط اجراشده را «رفتار تأییدشده» بنامیم، درصد از نظر حسابی ممکن است درست و از نظر تصمیمگیری نامعتبر باشد.
Coverage Claim با Quality Claim چه تفاوتی دارد؟
| ادعا | آنچه میتواند بگوید | آنچه بهتنهایی نمیتواند بگوید |
|---|---|---|
| Statement/Line Coverage | واحد تعریفشده توسط Collector مشاهده شده است | خروجی درست، سناریو کامل یا نقصی وجود ندارد |
| Branch Coverage | مقصدهای تعریفشدهٔ Branch طی Run دیده شدهاند | همهٔ شرطها، Pathها یا رفتارهای کسبوکار بررسی شدهاند |
| Requirement Mapping | رابطهای میان Requirement و Artifact ثبت شده است | تست اجرا شده، Pass معتبر یا Requirement درست است |
| Risk Evidence Coverage | برای ریسکهای واجد شرایط Evidence طبق قرارداد وجود دارد | ریسک حذف شده یا Release ایمن است |
| Mutation Score | تستها نسبت به مجموعهٔ انتخابشدهای از Mutantها حساس بودهاند | Oracle کامل، تست کافی یا محصول بدون نقص است |
برای سنجش جداگانهٔ قدرت آشکارسازی خطا، راهنمای عملی Mutation Testing و برای صحت Expected Result، قرارداد Test Oracle و Verdict را بخوانید. Reachability، Reveal Sensitivity و Decision Adequacy سه سؤال متفاوتاند.
چرا «۱۰۰٪» بدون مخرج یک جمله ناقص است؟
فرض کنید تیم الف ۹۳۰ خط از ۱۰۰۰ خط و تیم ب ۶۰۰ خط از ۶۰۰ خط را پوشش داده است. تیم ب ۱۰۰٪ و تیم الف ۹۳٪ دارد؛ اما تیم ب ۴۰۰ خط Generated، Adapter قدیمی و مسیر Feature Flag را بدون رجیستری از تحلیل خارج کرده است. سؤال درست این نیست که کدام عدد بزرگتر است؛ سؤال این است که Universe هر Claim چگونه ساخته شده و آیا Exclusionها مجاز، نسخهدار و قابلبازبینیاند.
| عدد یکسان | تعبیرهای ناسازگار محتمل |
|---|---|
| ۱۰۰٪ Line | هر خط حداقل یک Instruction دیده؛ یا هر Statement دیده؛ یا فقط فایلهای Includeشده دیده شدهاند |
| ۱۰۰٪ Branch | Branch ابزار الف؛ Arc ابزار ب؛ بدون Exception Branch؛ با/بدون Generated code |
| ۱۰۰٪ Requirement | فقط Link وجود دارد؛ یا طراحی Ready است؛ یا Evidence تازه و معتبر وجود دارد |
| ۱۰۰٪ Pass | تمام تستهای اجراشده Pass؛ تستهای Skip/Blocked حذف؛ یا فقط Retried final attempt گزارش شده |
در مستند رسمی Coverage Counterهای JaCoCo، کوچکترین واحد Instruction در bytecode است؛ Line به debug information وابسته است، کد synthetic کامپایلر میتواند نتیجهٔ غیرمنتظره بسازد و exception handling در تعریف Branch این ابزار شمرده نمیشود. در مقابل، مستند Branch Coverage در coverage.py از جفت مبدأ–مقصد خط و execution opportunity صحبت میکند. نتیجه: حتی دو گزارش با برچسب Branch، بدون Tool Contract الزاماً هممعنا نیستند.
قرارداد Coverage Claim: فیلدهای حداقلی
| بخش | فیلدهای لازم | پرسش ممیزی |
|---|---|---|
| Purpose | Claim ID/Version، Purpose، Decision، Audience | این درصد قرار است کدام تصمیم را بهتر کند؟ |
| Subject | Product، Repository، Commit، Build، Artifact digest، Cutoff | عدد دربارهٔ دقیقاً کدام شیء تغییرناپذیر است؟ |
| Semantics | Coverage type، Unit، Covered state، Claim limit | «پوششدادهشده» چه شرطی دارد؟ |
| Universe | Source، Query، Snapshot، Eligibility، Dedup، Digest | صورت و مخرج از یک Registry آمدهاند؟ |
| Measurement | Instrumentation، Collector/version، Config digest، Formula، Rounding | تولید عدد قابلبازتولید است؟ |
| Exceptions | Exclusion، N/A، Unknown، Stale، Invalid با Owner و Expiry | چیزی بیصدا ناپدید شده است؟ |
| Evidence | Raw/report artifact و digest، Run، زمان، Producer، Retention | Dashboard به Source Evidence برمیگردد؟ |
| Governance | Threshold rationale، Guardrail، Review due، Correction | با تغییر یا کشف خطا چه میکنیم؟ |
قالب آماده Coverage Claim
coverageClaim:
id: COV-CLAIM-024
version: 7
purpose: "دیدن Gap ساختاری تغییر Checkout"
decision: "اولویت بررسی و افزودن تست؛ نه تصمیم Release"
audience: [developer, tester, reviewer]
subject:
repository: checkout-api
commit: C24
build: BUILD-R24
artifactDigest: sha256:...
semantics:
type: branch
unit: jacoco-branch-counter
covered: "counter status FULLY_COVERED در همان Build"
claimLimit: "فقط Reachability؛ نه Oracle، کیفیت یا نبود نقص"
universe:
id: UNIV-COV-7
snapshotAt: 2026-08-13T07:30:00Z
eligiblePredicate: "production class included by CONFIG-9"
dedupKey: "class-id + method + branch-index"
digest: sha256:...
measurement:
collector: "JaCoCo pinned-version"
configDigest: sha256:...
covered: 581
denominator: 624
formula: "100 * covered / denominator"
percentage: 93.11
rounding: "half-up, two decimals"
exceptions:
excluded: 17
notApplicable: 9
unknown: 4
stale: 3
evidenceManifest: EVID-COV-24
owner: quality-capability-owner
reviewDue: 2026-08-20
status: READY_FOR_COVERAGE_CLAIM_REVIEW
نام ابزار یا Threshold بهتنهایی قرارداد نیست. نسخهٔ واقعی باید هویتهای سازمان شما، مسیر Artifact، سیاست نگهداشت، دسترسی و محدودیت تصمیم را نیز داشته باشد. مقدارهای بالا کاملاً مصنوعیاند.
گام اول: Purpose و Decision را قبل از درصد بنویسید
یک درصد ممکن است برای کشف Gap، بازبینی PR، نگهداشت Ratchet در Legacy، مقایسهٔ آزمایشی دو Strategy یا مشاهدهٔ Drift استفاده شود. هر Purpose دامنهٔ ادعا و واکنش متفاوتی دارد. «نمایش به مدیریت» Purpose کافی نیست؛ باید معلوم باشد مخاطب پس از دیدن Signal چه تصمیم مجازی میتواند بگیرد.
| Purpose | تصمیم مجاز | تصمیم غیرمجاز بدون Evidence دیگر |
|---|---|---|
| Gap discovery | انتخاب ناحیه برای تحلیل | رد خودکار Release |
| Changed-code guardrail | درخواست بررسی Gap تغییر | اعلام کیفیت کل محصول |
| Legacy ratchet | جلوگیری از افت تعریفشده | تحمیل هدف یکسان به همهٔ Moduleها |
| Experiment | مقایسه در Universe ثابت | رتبهبندی افراد یا تیمها |
| Risk evidence support | تشخیص Evidence ناقص | پذیرش خودکار ریسک |
گام دوم: Subject، Build و Cutoff را قفل کنید
Repository name کافی نیست. یک Claim باید Commit، Build ID، digest محصول قابلاجرا، Build profile، Source map و زمان Cutoff داشته باشد. Coverage از Run روی BUILD-R23 را نباید به BUILD-R24 نسبت داد؛ حتی اگر نام Branch یکی باشد. Merge، Rebase، Generated source، Optimization و Instrumentation میتوانند رابطهٔ Source و executable را تغییر دهند.
- Source identity: Repository، Commit SHA، submodule و generated-source recipe.
- Artifact identity: Build ID، binary/package digest، compiler/profile.
- Execution identity: Run ID، Suite version، Environment، Data Pack و زمان.
- Observation identity: Collector/version، agent mode، config digest و report digest.
گام سوم: Coverage Type و واحد شمارش را صریح کنید
| نوع | واحد نمونه | Covered semantics نمونه | محدودیت مهم |
|---|---|---|---|
| Instruction | bytecode instruction | Probe مرتبط اجرا شده | با خط Source یکی نیست |
| Line | source line قابل نگاشت | حداقل Instruction نسبتدادهشده اجرا شده | یک خط میتواند Partial باشد |
| Branch | counter/arc تعریف ابزار | مقصد یا Branch مشاهده شده | تعریف Exception و compiler متفاوت است |
| Function/Method | تابع یا method واجد شرایط | حداقل یک Instruction اجرا شده | داخل تابع و Oracle را نشان نمیدهد |
| Requirement | Requirement version | Evidence state مطابق قرارداد | Link با Evidence معتبر یکی نیست |
| Risk | Risk scenario version | Evidence تازه و bound موجود است | پوشش با حذف ریسک یکی نیست |
برای انتخاب Statement، Branch، Condition و Path بر اساس ساختار کد، مالک موضوع همان آموزش White-box Testing است. اینجا مهمتر از نام Counter، قرارداد قابلتکرار آن است.
گام چهارم: Coverage Universe Registry بسازید
Universe فهرست نسخهدار همهٔ واحدهایی است که پیش از دیدن نتیجه، نامزد مخرجاند. ساختن Universe بعد از Run امکان Cherry-picking دارد. Registry باید Source/query، snapshot time، eligible predicate، unit identity، dedup key، digest و Owner داشته باشد.
universeEntry: universeId: UNIV-COV-7 unitId: checkout.PaymentService#confirm:B14 unitType: jacoco-branch-counter sourceRef: src/payment/PaymentService.java@C24 eligibility: ELIGIBLE rationale: production-bytecode-in-scope state: NOT_COVERED evidenceRef: EVID-COV-24#counter-918 observedAt: 2026-08-13T07:34:12Z owner: checkout-team reviewDue: 2026-08-20
برای Requirement یا Risk نیز همین الگو برقرار است؛ Unit ID باید به نسخهٔ Basis یا Risk Registry متصل باشد. نگاشت کلی «REQ-۱۲ → test-payment» بدون Test version، Evidence state و Subject، Coverage معتبر نمیسازد.
Eligible Predicate؛ دروازه ورود به مخرج
Predicate باید قابلاجرا و پیشینی باشد؛ مثلاً «همهٔ Classهای Production در Artifact با namespace مشخص، بهجز Entryهای دارای Exclusion فعال». عبارتهایی مثل «کدهای مهم»، «موارد قابلتست» یا «سناریوهای لازم» بدون Registry و معیار، راه ورود سلیقه به مخرجاند.
| Predicate ضعیف | نسخهٔ ممیزیپذیر |
|---|---|
| فایلهای مرتبط | فایلهای production تحت paths-v4 در Commit C24 |
| کد Generated را حذف کن | artifactهایی با generator ID/version ثبتشده و Exclusion EX-۱۷ فعال |
| نیازمندیهای مهم | Risk tier ۱–۲ در RISK-v8 تا Cutoff مشخص |
| مرورگرهای لازم | Config tupleهای فعال در MATRIX-v5 با rationale و expiry |
Dedup؛ چرا یک واحد نباید چند بار رأی بدهد؟
Merge چند Report، اجرای موازی، Retry و چند Test layer ممکن است یک Unit را چند بار مشاهده کند. صورت باید «تعداد واحد یکتای پوششدادهشده» باشد، نه تعداد hit یا تعداد Test. Dedup key باید قبل از Merge تعریف شود. جمعزدن ۲۰۰ Branch از Shard اول و ۲۰۰ Branch از Shard دوم، وقتی ۱۵۰ Branch مشترکاند، ۴۰۰ واحد Coverage نمیسازد.
وضعیتهای لازم: Covered فقط یکی از آنهاست
| State | معنا | رفتار در محاسبه |
|---|---|---|
| COVERED | Covered semantics برای Subject همان Claim برقرار است | در صورت و مخرج |
| NOT_COVERED | واجد شرایط است اما Evidence پوشش ندارد | فقط در مخرج |
| PARTIAL | بخشی از Unit مرکب مشاهده شده | طبق قرارداد؛ هرگز خودکار Full |
| NOT_APPLICABLE | قاعدهٔ applicability برای این Subject صدق نمیکند | خارج از مخرج با rationale |
| EXCLUDED | با تصمیم حاکمیتی موقت/دائم کنار گذاشته شده | جداگانه گزارش شود |
| UNKNOWN | امکان تعیین وضعیت نیست | پنهان نشود؛ Claim را محدود کند |
| STALE | Evidence به نسخه/زمان معتبر تعلق ندارد | Covered جاری محسوب نشود |
| INVALID | Evidence یا اندازهگیری قواعد را نقض کرده | از Claim معتبر حذف و Incident ثبت شود |
Exclusion با Not Applicable یکی نیست
NOT_APPLICABLE یعنی خود قاعدهٔ Applicability دربارهٔ این Subject برقرار نیست. EXCLUDED یعنی واحد بالقوه در Scope بوده اما بر اساس Policy از اندازهگیری کنار گذاشته شده است. Generated code، Vendor library، Migration، fallback، unreachable code و Feature Flag خاموش بهطور خودکار هیچکدام N/A یا Excluded نیستند؛ هرکدام به تحلیل و ثبت نیاز دارند.
exclusion: id: EX-COV-017 universeId: UNIV-COV-7 unitIds: [generated.client.*] policyId: COV-POL-4 rationale: "منبع از schema نسخهدار تولید میشود" compensatingEvidence: generator-contract-run/E-72 requestedBy: build-owner reviewedBy: independent-reviewer effectiveFrom: 2026-08-13T00:00:00Z expiresAt: 2026-09-13T00:00:00Z status: ACTIVE
فایل config که با یک wildcard نیمی از Repository را حذف میکند، Evidence تصمیم نیست. تعداد و سهم Exclusion باید کنار درصد اصلی دیده شود؛ وگرنه بهینهسازی عدد با کوچککردن مخرج تشویق میشود.
Unknown و Stale را صفر نکنید
Collector crash، کلاسگذاری پویا، Source map ناقص، Run نیمهتمام، Artifact foreign یا Mapping نامشخص «Not covered» نیستند. اینها Unknown یا Invalid هستند. همچنین Evidence مربوط به Commit قبلی Stale است، حتی اگر رفتار ظاهراً تغییر نکرده باشد. تبدیل Unknown به Covered عدد را خوشرنگ و Claim را غیرقابلاعتماد میکند؛ تبدیل آن به Not covered نیز علت فنی را پنهان میسازد.
Instrumentation Contract؛ ابزار دقیقاً چه چیزی را دید؟
- نام و نسخهٔ Collector/Agent و حالت on-the-fly یا offline را Pin کنید.
- Include/Exclude، branch flag، plugin، compiler، optimization و debug info را digest کنید.
- رابطهٔ Source، generated code، bytecode/binary و Source map را ثبت کنید.
- پوشش Processهای child، worker، container، dynamic module و remote service را روشن کنید.
- نحوهٔ Merge فایلهای execution data، Retry و Shard را تعریف کنید.
- Collector failure، empty report، partial upload و version mismatch را FAIL/INVALID کنید؛ نه صفر یا ۱۰۰٪.
هدف، مقدسکردن ابزار نیست؛ هدف این است که بعداً یک Reviewer بتواند بفهمد Counter از کجا آمده است. ابزار فقط Observation میدهد و تصمیمگیر Coverage Contract است.
Coverage Evidence Manifest
evidenceManifest: id: EVID-COV-24 subjectId: checkout-api@C24 buildId: BUILD-R24 runId: RUN-COV-24 universeId: UNIV-COV-7 collector: jacoco/pinned-version configDigest: sha256:... rawArtifact: artifacts/RUN-COV-24/jacoco.exec rawDigest: sha256:... reportArtifact: artifacts/RUN-COV-24/report.xml reportDigest: sha256:... startedAt: 2026-08-13T07:31:00Z finishedAt: 2026-08-13T07:35:00Z producer: pipeline/COV-9 unknowns: [dynamic-plugin-P17] redaction: no-user-or-production-payload retention: 90d
Screenshot داشبورد، Source Evidence نیست؛ تصویر نه query و config را حفظ میکند، نه Counterهای خام را و نه امکان بازحساب دارد. Dashboard باید به Manifest و Artifact تغییرناپذیر لینک شود. برای اصول نمایش عدد بدون حذف Context، راهنمای Chart Contract و Visual QA مفید است.
فرمول، گردکردن و مخرج صفر
Coverage معمولاً از covered/total به دست میآید، اما قرارداد باید شمارش Partial، Unknown و واحد مرکب را مشخص کند. گردکردن فقط برای نمایش است. مقدار ۹۹٫۹۹۶٪ با گردکردن دو رقم ممکن است ۱۰۰٫۰۰٪ دیده شود، ولی Full coverage نیست. در Gate باید Counter خام یا شرط missedCount = ۰ استفاده شود.
if denominator == 0: result = EMPTY_UNIVERSE # نه 100 و نه 0 elif invalidEvidence: result = INVALID else: exactRatio = covered / denominator displayPercent = round(exactRatio * 100, 2) fullCoverage = (missed == 0 and partial == 0 and unknown == 0)
چرا میانگین درصدها اغلب اشتباه است؟
Module A با ۹۰/۱۰۰ برابر ۹۰٪ و Module B با ۱/۲ برابر ۵۰٪ است. میانگین ساده ۷۰٪ میشود، اما تجمیع درست Counterها ۹۱/۱۰۲ یا ۸۹٫۲۲٪ است. اگر Universeها overlap داشته باشند، حتی جمع Counter نیز قبل از Dedup معتبر نیست. وزندهی Risk یک تصمیم سیاستی جداست و نباید با Coverage ساختاری خام مخلوط شود.
| روش | نتیجه | اعتبار |
|---|---|---|
| (۹۰٪ + ۵۰٪) ÷ ۲ | ۷۰٪ | نامعتبر؛ اندازهٔ مخرج نادیده گرفته شده |
| (۹۰ + ۱) ÷ (۱۰۰ + ۲) | ۸۹٫۲۲٪ | فقط اگر Unit و Universe سازگار و بدون overlap باشند |
| Risk-weighted score | وابسته به وزن | Signal جدا با مدل/نسخه؛ نه جایگزین Counter خام |
Denominator Drift؛ وقتی درصد عوض میشود اما تست نه
اگر کد جدید اضافه، dead code حذف، Generator عوض، Requirement split یا Risk retire شود، مخرج Drift میکند. ممکن است تعداد Covered ثابت بماند ولی درصد بالا یا پایین برود. هر مقایسه باید numerator delta، denominator delta، علت تغییر Universe و Comparability را کنار هم نشان دهد.
| Baseline | Current | ظاهر | تفسیر درست |
|---|---|---|---|
| ۸۰۰/۱۰۰۰ = ۸۰٪ | ۸۰۰/۸۵۰ = ۹۴٫۱۲٪ | بهبود ۱۴٫۱۲ واحد | ۱۵۰ Unit از مخرج خارج شده؛ Coverage جدیدی ثبت نشده |
| ۸۰۰/۱۰۰۰ = ۸۰٪ | ۸۶۰/۱۱۰۰ = ۷۸٫۱۸٪ | افت ۱٫۸۲ واحد | ۶۰ Covered جدید و ۱۰۰ Unit جدیدِ باز وجود دارد |
| ۵۸۰/۶۲۰ | ۵۸۱/۶۲۴ | درصد نزدیک | نیازمند بررسی ۴ Unit جدید، یک Covered جدید و تعریف ثابت |
Comparability Decision؛ آیا دو گزارش قابل مقایسهاند؟
- COMPARABLE: Unit، Semantics، Collector/config و Universe policy ثابتاند؛ Drift توضیح داده شده است.
- RESTATED_BASELINE: با دادهٔ خام، Baseline تحت تعریف جدید دوباره محاسبه شده است.
- DIRECTIONAL_ONLY: اختلاف محدود وجود دارد و فقط جهت تقریبی قابلاستفاده است.
- NOT_COMPARABLE: تعریف، ابزار، Scope یا Evidence چنان عوض شده که Trend معتبر نیست.
آیا پوشش تست ۱۰۰٪ بد است؟
خیر. ۱۰۰٪ میتواند نتیجهای درست و مفید برای یک Universe محدود باشد؛ مثلاً تمام Branchهای یک تابع کوچک و حساس در Build مشخص مشاهده شدهاند. خطا زمانی رخ میدهد که از این گزارهٔ محدود به «همهٔ سناریوها تست شده»، «کیفیت عالی است»، «هیچ باگی نداریم» یا «Release امن است» جهش کنیم.
| ادعای مجاز نمونه | ادعای نامجاز |
|---|---|
| ۶۲۴/۶۲۴ Counter واجد شرایط طبق UNIV-۷ در BUILD-R24 مشاهده شد | تمام کد و تمام رفتارها درستاند |
| missedCount در این Report صفر است | هیچ مسیر اجرایی ممکنی باقی نمانده |
| Line coverage ابزار مشخص ۱۰۰٪ است | Branch، Condition، Requirement و Risk هم ۱۰۰٪ است |
| این Signal از Threshold عبور کرده | محصول Release-ready است |
چرا ۱۰۰٪ Line میتواند باگ واضح را نبیند؟
function payable(amount, fee) {
return Math.min(amount + fee, 10_000_000);
}
// خط اجرا میشود، اما هیچ خروجی بررسی نمیشود:
test('executes', () => { payable(900_000, 50_000); });
این Check میتواند خط را Reach کند، ولی اگر جمع به تفریق تغییر کند ممکن است همچنان Pass شود. Coverage پاسخ میدهد «کجا مشاهده شد؟»؛ Oracle پاسخ میدهد «چه رفتاری درست است؟»؛ Mutation میتواند بخشی از حساسیت Test Suite به تغییر را آزمایش کند. هیچکدام بهتنهایی تمام کیفیت محصول را اثبات نمیکنند.
درصد مناسب Code Coverage چند است؟
عدد جادویی عمومی وجود ندارد. ۸۰٪ و ۹۰٪ میتوانند Policy محلی باشند، نه حقیقت صنعت. Threshold را با Purpose، Criticality، معماری، Legacy baseline، Change surface، هزینهٔ نگهداشت و مکملهای Evidence تعیین کنید. برای منطق مالی کوچک ممکن است missed critical branch = ۰ مهمتر از Overall ۹۵٪ باشد؛ برای Adapter تولیدشده، Contract evidence شاید از تعقیب Line بیشتر ارزش داشته باشد.
| جزء Policy | نمونهٔ قابلدفاع |
|---|---|
| Purpose | جلوگیری از ایجاد Gap جدید در کد تغییرکرده |
| Signal | Changed-branch coverage تحت تعریف ثابت |
| Hard constraint | هیچ Critical risk branch ثبتشده بدون disposition نماند |
| Threshold | Ratchet نسبت به Baseline بازحسابشده؛ نه عدد کپیشده |
| Guardrail | زمان Suite، Flakiness، Assertion review و Exclusion share |
| Exception | Owner، rationale، compensating evidence و expiry |
| Claim limit | Signal بازبینی؛ نه Release یا Quality verdict |
Goodhart و بازی متریک: وقتی هدف، مخرج را میخورد
اگر پاداش یا ارزیابی افراد به درصد متصل شود، رفتار منطقی سیستم میتواند افزودن Checkهای کمارزش، حذف فایل، شکستن Unit، Mock بیشازحد، Retry تا Green یا انتخاب ابزار با Counter مساعد باشد. مقابله فقط با «فرهنگ بهتر» ممکن نیست؛ Metric باید محدودیت ادعا، Guardrail و منع رتبهبندی داشته باشد.
- درصد را برای تشخیص کار استفاده کنید، نه ارزیابی فرد.
- Numerator، denominator، Exclusion، Unknown و Drift را همزمان نمایش دهید.
- افزایش Coverage را کنار Mutation sample، Oracle review، Suite health و Cycle time ببینید.
- هر Exception را منقضی و هر تغییر config را Review کنید.
- بهبود عدد بدون بهبود تصمیم یا کاهش Unknown را موفقیت اعلام نکنید.
Coverage Gap یک دستور «تست بنویس» نیست
| Gap | Disposition محتمل | Evidence لازم |
|---|---|---|
| رفتار مهم بدون مشاهده | افزودن Test در Layer مناسب | Risk/Basis و Oracle |
| کد unreachable واقعی | حذف یا Refactor | تحلیل control flow و review |
| Generated adapter | Contract test یا Exclusion محدود | Generator identity و compensating evidence |
| Third-party boundary | Integration/contract evidence | Sandbox/fidelity limitation |
| Measurement failure | Repair Collector | Incident و rerun |
| ریسک پذیرفتهشده | Accept/defer زماندار | Decision owner، expiry و impact |
Layer را صرفاً برای بالا بردن عدد انتخاب نکنید. راهنمای هرم تست و طراحی سبد Unit، Integration و E2E کمک میکند Observation را در نزدیکترین Layer توانمند و Evidence نمایندهتر تکمیل کنید. برای اولویتگذاری نیز مالک موضوع تست مبتنی بر ریسک است.
Overall، Changed Code و Diff Coverage
Overall Coverage دربارهٔ Universe بزرگتری از محصول است؛ Diff/Changed-code Coverage دربارهٔ واحدهای منتسب به تغییر. دومی برای Feedback سریع مفید است، اما تغییر config، schema، dependency، feature flag یا رفتار کد قدیمی ممکن است در Diff خطی دیده نشود. هر دو Claim باید Universe و attribution rule مستقل داشته باشند و یکی جای دیگری را نگیرد.
Dashboard چه چیزی را باید کنار درصد نشان دهد؟
| لایه | نمایش حداقلی |
|---|---|
| Identity | Subject، Commit، Build، Run، Universe، Collector/config |
| Counters | Covered، missed، partial، denominator و درصد بدون گردکردن فریبنده |
| Validity | Unknown، stale، invalid، empty، report completeness |
| Scope | Included، excluded، N/A و denominator drift |
| Comparison | Baseline، numerator delta، denominator delta، comparability |
| Decision | Purpose، threshold rationale، guardrail، exception و claim limit |
| Evidence | Manifest، raw/report digest، producedAt و retention |
گزارش Status یا Release باید این Signal را در کنار Evidenceهای دیگر مصرف کند، نه اینکه آن را به Verdict نهایی تبدیل کند. برای این مرزبندی، راهنمای گزارش تست و تصمیم انتشار را ببینید.
Correction؛ اگر بعداً فهمیدیم درصد غلط بوده چه کنیم؟
Coverage Claim دادهٔ زندهٔ تصمیم است و باید اصلاحپذیر باشد. کشف config اشتباه، Artifact foreign، mapping ناقص، Exclusion نامعتبر یا formula bug فقط با اصلاح Dashboard تمام نمیشود؛ باید Claimهای متاثر، تصمیمهای مصرفکننده و Baselineهای Trend شناسایی شوند.
correction: id: CORR-COV-009 detectedAt: 2026-08-13T09:10:00Z cause: "گزارش BUILD-R23 به Claim BUILD-R24 متصل شده بود" invalidEvidence: EVID-COV-23 affectedClaims: [COV-CLAIM-024, DASH-SNAPSHOT-88] affectedDecisions: [REVIEW-421] containment: "Claimها INVALID و مصرف downstream متوقف شد" recalculation: RUN-COV-24R1 correctedResult: 93.11 republishedAt: 2026-08-13T10:20:00Z notified: [claim-owner, review-owner] prevention: "build digest equality gate" status: CLOSED
آزمایشگاه مصنوعی Checkout فارسی
برای جلوگیری از هرگونه ادعای واقعی، این آزمایشگاه کاملاً جدا و ساختگی است: `SYN-CHECKOUT` با Order، PaymentAttempt، PSP Stub، Callback، Ledger و Reconciliation جعلی. هیچ شبکه، Production، شرکت، کاربر، بانک، PSP واقعی، نام، موبایل، ایمیل، IP، حساب، PAN، CVV2، OTP، Cookie، Token، Credential، Screenshot یا Log واقعی در آن وجود ندارد.
| بعد | واحدهای مصنوعی | ریسک تفسیر |
|---|---|---|
| Money | IRR خیالی canonical؛ تومان فقط presentation و برچسبدار | تبدیل نمایشی با منطق Ledger اشتباه نشود |
| Digits | ارقام فارسی، عربی و لاتین | Line coverage تفاوت parsing را ثابت نمیکند |
| Unicode/RTL | NFC، RTL/LTR و stable ID | مشاهدهٔ Branch با صحت نمایش یکی نیست |
| Time | UTC instant، Asia/Tehran و جلالی صرفاً نمایشی | اجرای مسیر، صحت cutoff را اثبات نمیکند |
| State | timeout قبل/بعد fake commit، Callback تکراری/دیر/جابجا | ۱۰۰٪ Statement تمام interleavingها را پوشش نمیدهد |
| Isolation | Tenant/Order/Attempt/Event/Ledger/Run/Build/Evidence ID جدا | Merge بین Buildها Claim را Invalid میکند |
این Lab هیچ ادعای بانکی، مالی، حقوقی، مالیاتی، امنیتی، حریم خصوصی یا انطباق دربارهٔ ایران ندارد. هدف فقط نمایش خطای استنتاج از Reachability به Correctness است.
آزمایش اجرایی SYN-COVERAGE-CLAIM-DENOMINATOR-۰۱
یک Validator مستقل، dependency-free و قطعی با Node.js v26.۷.۰ اجرا شد. داشبورد سطحی ۶۲۴/۶۲۴ واحد، ۳۸۰/۳۸۰ تست Pass و ۱۰۰٪ Requirement mapped نشان میداد و Verdict آن PASS بود. Validator بهجای اعتماد به عدد، ۱۴۰ قاعده را روی Contract اعمال کرد.
| گروه قاعده | تعداد | نمونه کنترل |
|---|---|---|
| Identity | ۲۰ | Product، Risk، Basis، Commit، Build digest، Run، Collector |
| Claim | ۱۵ | Purpose، Decision، type، unit، semantics، claim limit، Owner |
| Universe | ۲۴ | Source/query/snapshot، eligibility، dedup، exclusion، Unknown، digest |
| Measurement | ۲۰ | Instrumentation، Counterها، formula، rounding، aggregation، time |
| Evidence | ۱۶ | Subject/Build/Run binding، raw/report artifact و digest، retention |
| Governance | ۱۵ | Threshold rationale، drift، comparability، exception، Correction |
| ادعاهای ممنوع | ۲۵ | ۱۰۰٪=کیفیت/بدون نقص/Release، هدف عمومی، ranking، AI approval |
| روابط بینفیلدی | ۵ | صورت≤مخرج، بازتولید درصد، Build/Universe/time binding |
نسخهٔ سطحی با ۱۳۳ Finding به HOLD رفت. هفت قاعده واقعاً Pass بودند: سه Counter و درصد ارائه شده بود و چهار رابطهٔ محدود بهعلت نبود داده به شکل مورد انتظار Validator نقض نشده بود؛ بنابراین برای ساختن عدد نمایشی، شمارش Findings دستکاری نشد. Findings شامل هویتهای مفقود، Claim بیPurpose، Universe بدون Registry، نبود config/raw digest، Exclusion و Unknown نامعلوم، Threshold بیمنطق، نبود Correction و ۲۵ استنتاج مطلق بود.
fixture: SYN-COVERAGE-CLAIM-DENOMINATOR-01 rules: 140 superficial dashboard: covered/denominator: 624/624 tests passed: 380/380 requirements mapped: 100% dashboard verdict: PASS contract verdict: HOLD findings: 133 corrected contract: covered/denominator: 581/624 display percentage: 93.11% missed: 31 partial: 12 excluded: 17 notApplicable: 9 unknown: 4 stale: 3 structural findings: 0 status: READY_FOR_COVERAGE_CLAIM_REVIEW
در نسخهٔ اصلاحشده، Subject و BUILD-SYN-۲۴، Universe نسخهدار UNIV-SYN-۷، Collector/config، Counterها، Evidence، Drift، Exception و Correction کامل و روابط بینفیلدی برقرار شدند. خروجی `READY_FOR_COVERAGE_CLAIM_REVIEW` عمداً فقط آمادگی ساختاری Claim برای بازبینی است؛ نه صدق Source یا Evidence، نه کفایت تست، نه قدرت Oracle، نه نبود نقص، نه کیفیت محصول و نه آمادگی انتشار.
پیادهسازی در CI/CD بدون ساختن Gate کور
collect raw execution data -> verify subject/build/config identity -> materialize versioned universe -> apply eligibility + dedup -> classify every unit state -> validate evidence completeness -> calculate raw counters and display percentage -> assess denominator drift + comparability -> apply contextual threshold + guardrails -> publish claim + manifest + limitations -> preserve correction path
اگر هر مرحله Unknown یا Invalid است، Pipeline نباید یک عدد قبلی را بهعنوان Current بازنشر کند. «Fail closed» همیشه به معنی Block Release نیست؛ یعنی وضعیت Claim را صادقانه INVALID/INCONCLUSIVE اعلام کنید و تصمیم Release را به Policy و Evidenceهای مستقل بسپارید.
Threshold، Guardrail و Exception را با هم طراحی کنید
| Signal | Countermetric/Guardrail | چرا لازم است؟ |
|---|---|---|
| Coverage delta | Denominator delta و Exclusion share | بهبود صوری با کوچککردن مخرج آشکار شود |
| Changed coverage | Config/schema/dependency impact review | تغییر غیرخطی از قلم نیفتد |
| Suite size | Duration، flakiness و maintenance cost | انباشت Check کمارزش پاداش نگیرد |
| Mutation sample | Equivalent/invalid mutant review | Score خام به کفایت تبدیل نشود |
| Requirement mapping | fresh valid evidence state | Link تزئینی پوشش محسوب نشود |
| Threshold pass | Critical gap و Unknown hard constraints | میانگین، شکاف مهم را نپوشاند |
اتوماسیون و AI کجا کمک میکنند و کجا نه؟
اتوماسیون میتواند Manifest بسازد، digest تطبیق دهد، Drift را محاسبه کند، Entry بدون Owner را بیابد و Claim را بازتولید کند. AI میتواند Gapها را خوشهبندی، توضیح اولیه یا Candidate disposition پیشنهاد کند؛ اما نباید Exclusion، N/A، پذیرش Risk، تغییر Threshold یا Release را خودکار تأیید کند. هر پیشنهاد باید Source، confidence، Unknown، Reviewer و audit log داشته باشد.
- هیچ کد، Prompt یا Artifact حساس را به سرویس تأییدنشده ارسال نکنید.
- دادهٔ کاربر/Production را برای ساخت مثال Coverage وارد نکنید.
- پیشنهاد AI را Evidence یا approval حساب نکنید.
- نسخهٔ مدل، Prompt/Policy و خروجی پذیرفته/ردشده را در محدودهٔ مجاز ثبت کنید.
- مسیر دستی و حق اعتراض به Exclusion یا ارزیابی تیم حفظ شود.
۳۰ ضدالگوی رایج پوشش تست
| ضدالگو | خطر | اصلاح |
|---|---|---|
| ۱۰۰٪ بدون نوع Counter | ابهام معنایی | Type/Unit/Semantics |
| نام Repository بدون Commit | Subject شناور | Commit/Build/digest |
| Dashboard بدون Manifest | غیرقابلبازتولید | raw/report artifacts |
| Screenshot بهجای Evidence | قطع lineage | لینک به Artifact |
| جمع hitها | تکرار صورت | unique unit + dedup |
| میانگین درصد Moduleها | نادیدهگرفتن اندازه | Counter aggregation |
| گردکردن ۹۹٫۹۹۶ به Full | Full کاذب | missed/partial raw |
| مخرج صفر برابر ۱۰۰ | موفقیت تهی | EMPTY_UNIVERSE |
| Unknown برابر Covered | اطمینان کاذب | UNKNOWN مستقل |
| Stale برابر Current | نسبتدادن Evidence قدیمی | freshness rule |
| Excluded برابر N/A | پنهانکردن تصمیم | State جدا |
| wildcard exclusion | کوچکشدن بیصدای مخرج | Registry/owner/expiry |
| Generated همیشه حذف | از دسترفتن ریسک Generator | تحلیل + compensating evidence |
| Unreachable همیشه بیخطر | dead/defensive code مبهم | حذف، Refactor یا rationale |
| Pass rate برابر Coverage | اختلاط Result و Reach | Signalهای جدا |
| Mapped برابر Tested | Link تزئینی | Evidence state |
| Executed برابر Asserted | Oracle ضعیف | Oracle review |
| ۱۰۰٪ برابر همهٔ Pathها | استنتاج بیشازحد | claim limit |
| ۱۰۰٪ برابر بدون نقص | Quality claim بیمدرک | Evidence مکمل |
| Threshold عمومی ۸۰/۹۰ | Policy بدون Context | rationale/baseline |
| Threshold تنها Gate | Critical gap پنهان | hard constraints |
| More is always better | هزینه و نگهداشت | Value + guardrail |
| Diff بهجای Overall | Risk خارج از خطوط | Claimهای مکمل |
| Overall بهجای Change | Gap PR محو میشود | changed-universe claim |
| Trend با Tool جدید | مقایسهٔ نامعتبر | restated baseline |
| Retry فقط آخرین attempt | پنهانکردن ناپایداری | attempt evidence |
| رتبهبندی تیمها | بازی و بیعدالتی | تشخیص سیستم، نه فرد |
| AI-approved exclusion | واگذاری اختیار | human accountable review |
| اصلاح Dashboard بدون Impact | تصمیمهای قدیمی آلوده | affected-claim analysis |
| Coverage بهعنوان Release verdict | فروکاست تصمیم | گزارش چندEvidenceای |
چکلیست ممیزی Coverage Claim
- Claim ID و Version یکتا دارد؟
- Purpose، Decision و Audience روشناند؟
- Product، Repository، Commit، Build و digest قفلاند؟
- Coverage type، Unit و Covered semantics تعریف شدهاند؟
- Claim limit از Quality و Release جداست؟
- Universe ID/version/source/query/snapshot موجود است؟
- Eligible predicate پیشینی و قابلاجراست؟
- Unit identity و Dedup key ثابتاند؟
- Included manifest و digest نگهداری شدهاند؟
- Exclusion registry، rationale، Owner و Expiry دارد؟
- N/A از Excluded جداست؟
- Unknown، Stale و Invalid صریحاند؟
- Generated، vendor، unreachable، dynamic و feature flag Policy دارند؟
- Collector/version و instrumentation mode Pin شدهاند؟
- Config، raw execution و report digest موجود است؟
- Covered، denominator، missed و partial خام دیده میشوند؟
- Formula، rounding، aggregation و empty rule ثبت شدهاند؟
- صورت از مخرج بزرگتر نیست و درصد بازتولید میشود؟
- Evidence به Subject، Build، Run و Universe همان Claim متصل است؟
- زمان، Producer، Unknown، retention و redaction معلوماند؟
- Denominator drift و علت تغییر گزارش شده است؟
- Comparability با Baseline تصمیمگیری شده است؟
- Threshold rationale و Guardrail وجود دارد؟
- Gap disposition و Exception زماندارند؟
- Correction، affected claims و republish path آزموده شدهاند؟
- درصد برای رتبهبندی فرد/تیم استفاده نمیشود؟
- AI فقط پیشنهاد میدهد و اختیار انسانی حفظ شده است؟
- Dashboard به Evidence خام لینک و محدودیت را نمایش میدهد؟
برنامه ۳۰ روزه برای اصلاح Coverage Reporting
| بازه | کار | خروجی قابلتحویل |
|---|---|---|
| روز ۱–۵ | انتخاب یک Claim و ثبت Purpose/Subject/Tool | Baseline و فهرست Unknown |
| روز ۶–۱۰ | ساخت Universe Registry و طبقهبندی Exclusion/N/A | Universe digest و denominator قابلبازتولید |
| روز ۱۱–۱۵ | اتصال raw/report Artifact و اعتبارسنجی روابط | Evidence Manifest و Validator |
| روز ۱۶–۲۰ | طراحی Threshold، Guardrail و Gap disposition | Policy نسخهدار و Exception workflow |
| روز ۲۱–۲۵ | نمایش Drift/Comparability و اجرای Correction drill | Baseline restatement یا NOT_COMPARABLE |
| روز ۲۶–۳۰ | Pilot در یک Repository و بازبینی تصمیمها | Keep/Adapt/Stop و Backlog محدود |
موفقیت Pilot را با رسیدن به درصد بالاتر نسنجید. معیار بهتر این است که آیا یک Reviewer مستقل توانست Claim را بازتولید کند، Unknownها کمتر شدند، Exclusion بیمالک نماند، Drift توضیح داده شد و یک تصمیم اشتباه پیش از مصرف اصلاح شد.
جمعبندی: درصد را دور نریزید؛ ادعایش را محدود کنید
پوشش تست ۱۰۰٪ سراب نیست؛ یک مشاهدهٔ محدود است که بدون قرارداد میتواند شبیه سراب مصرف شود. عدد زمانی ارزش دارد که مخرج آن نسخهدار، صورت آن یکتا، Semantics آن روشن، Evidence آن همBuild، استثناهایش آشکار و Correction آن عملی باشد. آنوقت ۱۰۰٪ میگوید «در Universe تعریفشده Gap مشاهدهنشده نداریم»—و صادقانه سکوت میکند دربارهٔ کیفیت، Oracle، سناریوهای غایب، نقصهای ناشناخته و Release.
سوالات متداول درباره پوشش تست ۱۰۰٪
آیا پوشش تست ۱۰۰٪ یعنی نرمافزار بدون باگ است؟
خیر. ۱۰۰٪ فقط میتواند بگوید تمام واحدهای واجد شرایط در Universe و تحت Covered semantics مشخص مشاهده شدهاند. این عدد درستی Oracle، کاملبودن Basis، همهٔ ورودیها، concurrency، امنیت، کارایی، تجربهٔ کاربر یا نبود نقص را اثبات نمیکند.
درصد مناسب Code Coverage برای پروژه چقدر است؟
عدد عمومی وجود ندارد. Threshold باید از Purpose، Risk، Criticality، معماری، Baseline، هزینه و Evidence مکمل ساخته شود. بهجای کپی ۸۰٪ یا ۹۰٪، hard constraintهای Critical، Ratchet کد تغییرکرده و Guardrailهای سلامت Suite را تعریف کنید.
تفاوت Line Coverage و Branch Coverage چیست؟
Line Coverage مشاهدهٔ واحدهای خط را طبق تعریف ابزار میسنجد؛ Branch Coverage مقصدها یا Counterهای تصمیم را. اجرای تمام خطوط الزاماً هر خروجی تصمیم را نمیبیند. تعریف دقیق نیز ابزارمحور است؛ پس نام ابزار، نسخه، config و Covered semantics را ثبت کنید.
آیا موارد Excluded را باید از مخرج حذف کنیم؟
فقط طبق Policy نسخهدار و با Entry شامل Unit، rationale، Owner، Reviewer، compensating evidence و Expiry. سهم Exclusion باید کنار درصد دیده شود. Excluded را با NOT_APPLICABLE یا UNKNOWN یکی نکنید و تغییر آن را Denominator Drift گزارش دهید.
آیا Mutation Testing جایگزین Code Coverage است؟
خیر. Code Coverage بیشتر Reachability و Mutation Testing حساسیت نسبت به مجموعهای از تغییرهای مصنوعی را بررسی میکند. هر دو محدودیت و مخرج خود را دارند و در کنار Oracle review، Risk evidence و Test portfolio معنا پیدا میکنند؛ هیچکدام Verdict کیفیت یا Release نیستند.

