تست وب‌اپلیکیشن؛ از Unit و Contract تا End-to-End و Release

همه تست‌ها سبز بودند، اما با دوبار رسیدن Callback درگاه، یک سفارش دو بار «پرداخت‌شده» شد و انبار دو بار کم شد. Unit test محاسبه مبلغ درست بود؛ تست رابط هم پیام موفقیت را می‌دید. باگ در مرز واقعیِ شبکه، تکرار پیام و State transition پنهان شده بود. این نمونه نشان می‌دهد کیفیت وب‌اپلیکیشن با تعداد Test case، درصد Coverage یا نصب یک ابزار تضمین نمی‌شود؛ باید ریسک‌های محصول را به یک سبد تست سریع، معتبر و قابل‌نگهداری تبدیل کرد.

این راهنما از انتخاب Test level تا Release gate و بازخورد Production پیش می‌رود. مثال‌ها با TypeScript نوشته شده‌اند، اما منطق آن‌ها برای PHP، Python، Java و دیگر Stackها یکسان است. هدف، نسخه‌پیچی «حتماً Jest یا Cypress» نیست؛ هدف ساختن Confidence با کمترین هزینه بازخورد است.

تست نرم‌افزار چیست و چه چیزی را تضمین نمی‌کند؟

تست نرم‌افزار جمع‌آوری شواهد درباره رفتار سیستم در شرایط تعریف‌شده است. تست موفق نشان می‌دهد سناریوی مشخص با داده، محیط و نسخه مشخص مطابق انتظار عمل کرده است؛ اثبات نمی‌کند سیستم در همه ورودی‌ها و شرایط بدون نقص است. بنابراین واژه «تضمین کیفیت» را باید قراردادی فهمید: چه ویژگی‌هایی، در برابر چه ریسک‌هایی، با چه سطح شواهدی و تا چه زمان؟

Quality فقط Correctness نیست. امنیت، دسترس‌پذیری، کارایی، قابلیت بازیابی، سازگاری مرورگر، مشاهده‌پذیری و تجربه کاربر نیز کیفیت‌اند. یک سبد خرید که مبلغ را درست حساب می‌کند اما با Keyboard قابل استفاده نیست یا در شبکه ضعیف سفارش را گم می‌کند، محصول باکیفیتی نیست.

قرارداد کیفیت را از Outcome و SLO شروع کنید

پیش از انتخاب فریم‌ورک، Critical journeyها و Failure outcomeها را بنویسید. برای یک فروشگاه ایرانی، «جست‌وجو→سبد→پرداخت→تأیید→کاهش موجودی→اعلان» یک Journey است. قرارداد کیفیت می‌تواند بگوید سفارش تأییدشده نباید گم یا Duplicate شود، وضعیت نامعلوم باید قابل Reconcile باشد، قیمت به ریال/تومان اشتباه نشود و کاربر پس از بازگشت از PSP مسیر بازیابی داشته باشد.

جزء قراردادپرسشنمونه شواهد
Outcomeکاربر/کسب‌وکار چه نتیجه‌ای می‌خواهد؟یک سفارش معتبر و قابل پیگیری
Invariantsچه چیزی هرگز نباید بشکند؟یک transaction_id فقط یک بار اثر مالی دارد
SLO/Thresholdحد قابل‌پذیرش چیست؟p95 پاسخ، Error budget، نرخ خطای Checkout
Environmentدر کدام Browser/Device/Network؟ماتریس مبتنی بر RUM و کاربران واقعی
Recoveryشکست چگونه تشخیص و جبران می‌شود؟Retry امن، Reconciliation و Runbook
Ownerچه کسی نگهداری و تصمیم را دارد؟تیم Feature، نه «فقط QA»

Risk map تعیین می‌کند چه چیزی را کجا تست کنید

هر قابلیت را بر اساس احتمال شکست، شدت پیامد، کشف‌پذیری و دفعات تغییر امتیاز دهید. پرداخت، Authorization، حذف داده و Migration معمولاً ریسک بالاتری از تغییر یک متن تزئینی دارند. ریسک بالا الزاماً E2E بیشتر نمی‌خواهد؛ شاید Invariant در Unit، تراکنش در Integration و Callback واقعی در Sandbox هرکدام بخشی از خطر را ارزان‌تر پوشش دهند.

Risk score = Likelihood × Impact × Exposure × Change frequency

For each risk:
failure mode → cheapest credible test → release gate
             → production signal → recovery action

ماتریس را در Refinement به‌روزرسانی کنید. Incident تولید، تغییر Provider، رشد ترافیک و ورود یک مسیر مالی جدید باید Portfolio تست را تغییر دهد.

به‌جای تقلید هرم، Test portfolio متناسب بسازید

هرم تست یک استعاره برای بازخورد سریع و تعداد کمتر تست‌های Broad است، نه نسبت ثابت. مقاله Practical Test Pyramid نیز سطح‌ها را با Granularity و مرزهای Integration/Contract توضیح می‌دهد و بر حذف تکرار تأکید می‌کند. در Frontend سنگین ممکن است Component/Browser test سهم بیشتری داشته باشد؛ در Data pipeline، Contract و Data-quality test مهم‌تر است.

لایهبهترین سؤالوابستگی واقعیریسک نگهداری
Staticآیا Type/Rule/Dependency نقض شده؟بدون Runtime کاملکم
Unitمنطق در ورودی‌های مرزی درست است؟کم یا صفرکم
ComponentUI در برابر رفتار کاربر درست است؟DOM/Browser محدودکم تا متوسط
Integrationکد با DB/Cache/Queue/File درست کار می‌کند؟مرز واقعی منتخبمتوسط
ContractConsumer و Provider هنوز هم‌زبان‌اند؟قرارداد Versionedمتوسط
E2EJourney حیاتی روی سیستم مستقر کامل است؟کل Stack منتخببالا

Testability نتیجه طراحی است. Port/Adapter و جداسازی منطق از I/O، انتخاب سطح مناسب را آسان می‌کند؛ راهنمای معماری تمیز در وب این مرزها را باز می‌کند.

قبل از Test case، مثال و Invariant بنویسید

یک تست خوب رفتار قابل‌مشاهده را با Arrange–Act–Assert یا Given–When–Then توضیح می‌دهد. فقط Happy path کافی نیست. Partitionها، Boundaryها، حالت خالی، Duplicate، زمان، Race، Permission، Currency، Locale و Failure dependency را فهرست کنید.

Techniqueنمونه در وب‌اپباگ هدف
Boundary value۰، ۱، سقف تعداد کالاOff-by-one
Equivalence partitionکاربر مهمان/عضو/ادمینPermission branch
State transitionpending→paid→refundedTransition نامعتبر/تکراری
Decision tableکد تخفیف × شهر × روش ارسالترکیب قانون‌ها
Property/invariantجمع اجزا هرگز منفی نیستفضای ورودی گسترده
Metamorphicترتیب نمایش نباید جمع را عوض کندنبود Oracle ساده

Unit test را روی منطق پایدار و مرزهای پرارزش متمرکز کنید

Unit تعریف جهانی ندارد؛ مهم این است که تست سریع، تعیین‌پذیر و دارای علت شکست محدود باشد. تابع قیمت‌گذاری را از ساعت، شبکه و دیتابیس جدا کنید. مبلغ را با عدد صحیح در کوچک‌ترین واحد پول نگه دارید و Policy گردکردن را صریح کنید.

import { describe, expect, it } from "vitest";

describe("finalAmountRial", () => {
  it.each([
    { subtotal: 0, discount: 0, shipping: 0, expected: 0 },
    { subtotal: 1_000_000, discount: 100_000, shipping: 50_000,
      expected: 950_000 }
  ])("keeps the money invariant: $expected", (x) => {
    expect(finalAmountRial(x)).toBe(x.expected);
  });

  it("rejects a discount greater than eligible subtotal", () => {
    expect(() => finalAmountRial({
      subtotal: 100_000, discount: 110_000, shipping: 0
    })).toThrow("INVALID_DISCOUNT");
  });
});

تست نام تابع خصوصی یا ترتیب فراخوانی داخلی معمولاً Refactor را بی‌دلیل می‌شکند. رفتار Public و Invariant را بسنجید. اگر برای هر خط نیاز به Mock دارید، مرز طراحی احتمالاً مبهم است.

Component test باید مثل کاربر Query کند

در UI، به‌جای CSS selector شکننده و State داخلی، Role، Accessible name، Label و رفتار قابل‌مشاهده را هدف بگیرید. مستند Testing Library نیز تست نزدیک به شیوه استفاده کاربر و پرهیز از Implementation detail را اصل می‌داند.

render(<LoginForm />);
await user.type(screen.getByLabelText(/شماره موبایل/), "09120000000");
await user.click(screen.getByRole("button", { name: /ورود/ }));
expect(await screen.findByRole("alert")).toHaveTextContent(
  /کد تأیید ارسال شد/
);

این رویکرد هم‌زمان Label و Role را تحت فشار می‌گذارد، اما اثبات WCAG نیست. Visual snapshot نیز فقط تغییر Pixel را آشکار می‌کند؛ درست‌بودن محتوا، Keyboard flow و منطق کسب‌وکار به Assertionهای دیگری نیاز دارد.

Integration test را با Dependency واقعی و Scope باریک اجرا کنید

Mock دیتابیس نشان نمی‌دهد Constraint، Transaction، Collation، Time zone یا Query واقعی درست است. برای Repository یا Migration، نسخه واقعی DB را در Container اجرا کنید؛ Schema را همان Artifact تولید بسازید؛ و هر تست داده مستقل داشته باشد. Queue، Cache و Object storage نیز در صورت ریسک بالا باید حداقل در یک لایه واقعی آزمایش شوند.

  • Rollback/Commit و Deadlock/Retry را تست کنید.
  • Unique constraint و Idempotency key را در DB اثبات کنید.
  • Migration forward و در صورت ادعا rollback/restore را روی Snapshot ناشناس تمرین کنید.
  • Clock، UUID و Random را Inject کنید تا تست تعیین‌پذیر بماند.
  • سرویس خارجی را در این لایه Stub کنید، اما Fidelity آن را با Contract بسنجید.

Contract test شکاف Consumer و Provider را زودتر می‌بندد

Schema validation به‌تنهایی Semantics را کامل نمی‌کند، اما تغییر Field، Status، Event و Error contract را زود آشکار می‌سازد. Consumer-driven contract نیازهای واقعی Consumer را ثبت و Provider همان قرارداد را در Build خود Verification می‌کند؛ مستند Pact جریان تولید Contract و Replay روی Provider را توضیح می‌دهد.

برای REST/GraphQL، Request/response، status، header، nullability و backward compatibility را ثبت کنید. برای Event، نام Topic، Schema version، key، ordering، idempotency و replay semantics لازم است. Contract test جای Integration واقعی یا E2E را نمی‌گیرد؛ ریسک ناسازگاری مرز را ارزان‌تر جدا می‌کند. طراحی قرارداد API در راهنمای API وب، امنیت و قابلیت اطمینان تکمیل شده است.

E2E را به Journeyهای حیاتی و Release smoke محدود کنید

E2E بیشترین سطح اتصال را می‌بیند، اما علت شکست را سخت‌تر مشخص می‌کند و به محیط، شبکه و داده حساس است. پاس‌شدن آن «بالاترین تضمین» عمومی نیست؛ فقط برای Journey پوشش‌داده‌شده شواهد Broad می‌دهد. Edge caseهای فراوان را در لایه پایین‌تر نگه دارید و در E2E مسیرهای درآمد، ورود، Permission، ایجاد/ویرایش حیاتی و Recovery را پوشش دهید.

test("پرداخت ناموفق قابل بازیابی است", async ({ page }) => {
  await page.goto("/checkout");
  await page.getByRole("button", { name: "پرداخت" }).click();
  await fakePsp.returnFailure("TIMEOUT");
  await expect(page.getByRole("alert")).toContainText("وضعیت نامشخص");
  await expect(page.getByRole("button", { name: "بررسی دوباره" }))
    .toBeVisible();
});

Playwright پیش از Action، Actionability را بررسی و Assertionها را Auto-retry می‌کند؛ مستند Auto-waiting رفتار دقیق آن را شرح می‌دهد. این قابلیت جای Root-cause analysis را نمی‌گیرد و Retry کور، Flaky test را سالم نمی‌کند.

Checkout ایرانی را با State machine و Failure injection تست کنید

مسیر پرداخت فقط Redirect موفق نیست. کاربر ممکن است صفحه PSP را ببندد، Callback دیر یا دوبار برسد، Verify Timeout شود، مبلغ ریال/تومان اشتباه شود یا پاسخ بانک پس از Refresh تکرار گردد. سناریوهای زیر را در Sandbox و یک محیط نزدیک Production اجرا کنید:

سناریوانتظارشاهد
Callback تکراریاثر مالی و موجودی فقط یک بارIdempotency + DB constraint
Callback بدون بازگشت Browserسفارش از Webhook/Verify نهایی شودIntegration/E2E
Timeout در VerifyUnknown، نه Failed قطعیState test + reconciliation
مبلغ ×۱۰Mismatch پیش از ثبت پرداخت رد شودContract + fixture ریال/تومان
Refresh موفقDuplicate event/order نسازدE2E + telemetry assertion
لغو/بازگشتسبد و پیام Recovery حفظ شودCross-browser journey

برای طراحی خود Checkout و شاخص‌های آن، راهنمای بهینه‌سازی صفحه پرداخت مرز مکمل است.

Test double را بر اساس هدف انتخاب کنید

Dummy فقط آرگومان لازم را پر می‌کند؛ Stub پاسخ از پیش تعیین‌شده می‌دهد؛ Spy تعامل را ثبت می‌کند؛ Mock انتظار تعامل را Verification می‌کند؛ Fake پیاده‌سازی سبک اما کارا مانند In-memory repository است. اصطلاح‌ها میان جوامع متفاوت‌اند؛ در تیم یک واژه‌نامه محلی بسازید.

هر Double یک فرض درباره واقعیت دارد. اگر API واقعی Header تازه، Rate limit یا Semantics جدید بگیرد، Stub قدیمی همچنان سبز می‌ماند. Contract test، Sandbox smoke و تاریخ آخرین Validation را کنار Fake نگه دارید. سرویس‌هایی مثل ایمیل یا PSP را برای Unit/Integration ایزوله کنید، اما مرز واقعی را هرگز فقط با Mock «اثبات‌شده» فرض نکنید.

محیط تست باید قابل بازتولید و نزدیک به قرارداد تولید باشد

عبارت «روی سیستم من پاس است» معمولاً اختلاف Artifact، Config، Dependency، Time zone یا داده را پنهان می‌کند. Dependencyها را Lock، Runtime و Browser را Pin، Image را Immutable و Configuration را Version کنید. Staging کپی اسرار یا داده شخصی Production نیست؛ داده باید مصنوعی یا ناشناس و دسترسی حداقلی باشد.

  • همان Artifactی را Promote کنید که تست شده است؛ در هر محیط Rebuild نکنید.
  • Migration را پیش از Application و با Compatibility window مناسب بیازمایید.
  • Feature flag و Schema version را در گزارش تست ثبت کنید.
  • وابستگی‌های شبکه را با Fault injection محدود: latency، timeout، ۴۲۹ و 5xx آزمایش کنید.
  • Seed داده، Clock و Locale را تعیین‌پذیر کنید؛ تهران/UTC و تقویم/متن فارسی را Fixture داشته باشید.

Test data باید مستقل، حداقلی و قابل پاک‌سازی باشد

تست نباید به ترتیب اجرای قبلی یا حساب مشترک وابسته باشد. هر تست رکوردهای موردنیاز خود را بسازد و پس از اجرا پاک کند یا در Namespace یکتا قرار دهد. اجرای موازی بدون جداسازی داده، Flake و تداخل موجودی می‌سازد.

برای داده حساس از Dump خام Production استفاده نکنید. Masking سطحی ممکن است رابطه‌ها یا PII پنهان در متن آزاد و Log را باقی بگذارد. Synthetic data با Constraintهای واقعی، حالت‌های فارسی مانند نیم‌فاصله، اعداد فارسی/لاتین، +۹۸، کدپستی، نام طولانی و آدرس چندخطی را پوشش دهد.

Flaky test را سیگنال نقص سیستم تست بدانید

Flaky test بدون تغییر کد گاهی پاس و گاهی Fail می‌شود. علت می‌تواند Race، زمان واقعی، Animation، Selector شکننده، داده مشترک، وابستگی بیرونی، Port conflict، Resource starvation یا Cleanup ناقص باشد. Retry می‌تواند اطلاعات تشخیصی جمع کند، اما تبدیل Fail به Pass بی‌صدا اعتماد را از بین می‌برد.

  1. تست را با Owner و Issue قرنطینه کنید؛ Gate را بدون ثبت دور نزنید.
  2. Trace، Screenshot، Video، Console، network و server log را Artifact کنید.
  3. به‌صورت تکراری، Shuffle و با Workerهای متفاوت بازتولید کنید.
  4. Sleep ثابت را با انتظار روی وضعیت قابل‌مشاهده جایگزین کنید.
  5. ریشه Race یا Isolation را رفع و تست Quarantine را با معیار خروج بازگردانید.
Flake rate = inconsistent outcomes / repeated identical runs
Quarantine must have: owner + reason + issue + expiry
Retry result must remain visible in CI analytics

Coverage نقشه خلأ است، نه نمره کیفیت

Line/branch/function coverage می‌گوید چه کدی در تست اجرا شده، نه اینکه Assertion درست یا Risk مهم پوشش داده شده است. عدد ۷۰ یا ۸۰ درصد «استاندارد عمومی صنعت» نیست. یک Threshold می‌تواند از افت ناگهانی جلوگیری کند، اما باید بر ماژول و ریسک تنظیم شود؛ کد مالی و Authorization با Adapter ساده یک انتظار ندارند.

برای سنجش کیفیت Assertion از Mutation testing به‌صورت هدفمند استفاده کنید: ابزار تغییرهای کوچک در کد می‌دهد و می‌سنجد تست آن‌ها را می‌کشد یا نه. Mutation survivor الزاماً باگ نیست، اما سؤال خوبی می‌سازد. Pair کنید: Risk coverage، Branch coverage، Mutation score، Defect escape و زمان بازخورد.

Release gate را سریع، مرحله‌ای و قابل توضیح بسازید

همه تست‌ها را روی هر Save اجرا نکنید. Local loop باید ثانیه‌ای، Pull Request loop چنددقیقه‌ای، و Broad suite زمان‌بندی‌شده یا Pre-release باشد. تغییرات پرریسک می‌توانند Gate اضافی فعال کنند.

مرحلهنمونه Gateهدف زمانArtifact
Local/pre-commitFormat، lint، type، unit متاثرثانیه‌هاخطای مستقیم
Pull requestunit/component/integration/contractچند دقیقهcoverage، contract، log
Preview/Stagingmigration، smoke، E2E حیاتی، a11y scanمحدود و موازیtrace/video/report
Scheduledbrowser matrix، performance، security scanطولانی‌ترtrend/baseline
Productionsynthetic smoke، canary، SLOپیوستهtelemetry/rollback signal

راهنمای CI/CD امن وب‌اپلیکیشن Provenance، Progressive delivery و Rollback این فرایند را پوشش می‌دهد. Gate باید علت و مسیر رفع روشن داشته باشد؛ Skip مبهم به بدهی دائمی تبدیل می‌شود.

تست Performance را با Budget و Workload واقعی تعریف کنید

«صفحه سریع است» Testable نیست. مسیر، Dataset، Cache state، تعداد کاربر، نرخ ورود، منطقه، Device و Threshold را مشخص کنید. تست Microbenchmark برای تابع، Load test برای سرویس، Stress برای نقطه شکست، Soak برای Leak و RUM برای تجربه واقعی پرسش‌های متفاوتی دارند.

Test lab جای Field را نمی‌گیرد. Synthetic regression را با Telemetry تولید و Core Web Vitals واقعی ترکیب کنید؛ راهنمای Core Web Vitals و RUM قرارداد LCP/INP/CLS را باز می‌کند. Performance gate باید نسبت به Baseline و Noise محیط مقاوم باشد، نه یک عدد بی‌زمینه.

Security testing یک Suite موازی با Threat model است

Happy path امنیت را اثبات نمی‌کند. برای هر Trust boundary، احراز هویت، مجوزدهی شیء/عمل، Validation، Rate limit، Secret، Session و Audit را با تست منفی بسنجید. SAST، dependency/secret scan، DAST و Penetration test پوشش‌های متفاوت دارند.

OWASP Web Security Testing Guide چارچوب گسترده‌ای برای آزمون امنیت وب و سرویس‌ها ارائه می‌کند؛ Scope، نسخه و شناسه Scenario را ثبت کنید. تست نفوذ یک Snapshot است و جای کنترل‌های SDLC را نمی‌گیرد؛ راهنمای تست نفوذ وب‌اپلیکیشن Rules of Engagement، Evidence و Retest را تفکیک می‌کند.

Accessibility testing را فقط به Scanner نسپارید

Automation می‌تواند بخشی از خطاهای ساختاری مانند نام قابل‌دسترسی یا Contrast را پیدا کند، اما منطقی‌بودن Focus order، فهم پیام خطا، کار با Screen reader و کیفیت مسیر کامل به ارزیابی انسانی نیاز دارد. W3C صریحاً می‌گوید ابزار به‌تنهایی انطباق را تعیین نمی‌کند و ارزیابی انسانی آگاه لازم است؛ مرجع Evaluating Web Accessibility نقطه شروع رسمی است.

در Component test از Role/Label استفاده کنید؛ در E2E مسیر Keyboard، Focus، Dialog، Error summary و Live region را بپیمایید؛ و در Releaseهای مهم با کاربر و فناوری کمکی ارزیابی کنید. برای فرایند کامل، ممیزی دسترس‌پذیری وب را ببینید.

Usability test چیزی را می‌سنجد که Automation نمی‌فهمد

تست خودکار می‌گوید دکمه قابل کلیک و پاسخ ۲۰۰ است؛ نمی‌گوید کاربر عنوان را می‌فهمد، Offer را مقایسه می‌کند یا بعد از خطا اعتمادش را حفظ می‌کند. Task completion، خطای کاربر، زمان، مسیر و گفتار شرکت‌کننده به سؤال دیگری پاسخ می‌دهند.

Automation و Usability رقیب نیستند. پیش از جلسه پژوهش، Build را با Smoke پایدار کنید؛ پس از کشف الگوی مشکل، بخش Deterministic آن را به Regression test تبدیل کنید. روش نمونه‌گیری، Task و تحلیل در راهنمای تست کاربردپذیری آمده است.

Production بخشی از سیستم شواهد است، نه محیط تست کاربران

هیچ محیطی همه تنوع تولید را شبیه‌سازی نمی‌کند. Telemetry باید همان Invariant و Outcome قرارداد کیفیت را ببیند: نرخ سفارش Unknown، Duplicate callback، خطای Permission، p95 مسیر، Queue lag و شکستن SLO. Correlation ID را از Browser تا API و Job حفظ کنید.

Canary، Feature flag و Progressive rollout دامنه آسیب را محدود می‌کنند؛ جای تست پیش از انتشار نیستند. Synthetic smoke فقط داده علامت‌گذاری‌شده و قابل پاک‌سازی بسازد. Alert باید Runbook، Owner و امکان Rollback یا Disable flag داشته باشد. Incident تولید نیز باید به Regression test در ارزان‌ترین لایه معتبر تبدیل شود.

مالکیت کیفیت را میان نقش‌ها تقسیم کنید

توسعه‌دهنده مالک Unit/Component/Integration همان Feature است؛ Product و Domain expert مثال و Acceptance را می‌سازند؛ QA/Test engineer در Risk، Test design، Exploratory و سیستم کیفیت تخصص دارد؛ Security، Accessibility و SRE شواهد تخصصی اضافه می‌کنند. انتقال همه مسئولیت به یک تیم QA در انتهای Sprint، بازخورد را دیر و Context را کم می‌کند.

Test code کد Production-adjacent است: Review، naming، abstraction محدود، dependency update، observability و حذف تست منسوخ لازم دارد. نسبت «تعداد باگ یافته‌شده توسط QA» معیار مناسبی برای ارزش فرد نیست و انگیزه همکاری زودهنگام را خراب می‌کند.

TDD و BDD ابزارهای گفت‌وگو هستند، نه مراسم اجباری

چرخه Red–Green–Refactor می‌تواند طراحی API و Feedback loop را بهتر کند، به‌ویژه برای منطق قابل مثال. اما نوشتن Test-first برای Spike اکتشافی یا UI نامطمئن همیشه کم‌هزینه‌ترین راه نیست. پس از یادگیری، Spike را دور بیندازید یا رفتار پایدار را پوشش دهید.

BDD وقتی ارزش دارد که مثال Given/When/Then میان Product، Engineering و QA ابهام Domain را کم کند. تبدیل همه Test caseها به متن طولانی Gherkin بدون مشارکت ذی‌نفع، فقط لایه نگهداری تازه‌ای می‌سازد. Scenario باید Outcome و Rule را توضیح دهد، نه جزئیات کلیک‌های UI را.

برنامه ۳۰روزه برای وب‌اپ Legacy

روزهای ۱ تا ۵: Inventory و Baseline

Critical journey، Incidentهای اخیر، وابستگی‌ها، زمان Suite، Flake rate و Defect escape را ثبت کنید. دو Risk مهم را انتخاب کنید؛ پروژه را با هدف Coverage عمومی شروع نکنید.

روزهای ۶ تا ۱۲: Characterization و Seam

برای رفتار Legacy پیش از تغییر Characterization test بنویسید. Clock، HTTP client و Repository را پشت Seam ببرید. اولین Integration test را با DB واقعی و داده مستقل بسازید.

روزهای ۱۳ تا ۱۸: Golden journey و Contract

یک E2E از Journey درآمد/خدمت و Contract حیاتی Provider را اضافه کنید. Trace و Screenshot شکست را در CI نگه دارید. Flakyها را با Owner قرنطینه کنید.

روزهای ۱۹ تا ۲۴: Gate و Non-functional

PR gate سریع، Migration smoke، a11y scan و حد Performance منتخب را فعال کنید. Secret/dependency scan و تست منفی Permission را اضافه کنید.

روزهای ۲۵ تا ۳۰: Production feedback

Canary/Synthetic smoke، Invariant alert و Runbook Rollback را وصل کنید. زمان Feedback، Flake و Defect escape را مرور و Backlog بعدی را با Risk اولویت‌بندی کنید.

Definition of Done را قابل اثبات کنید

  • Acceptance examples و Failure modeهای مهم نوشته شده‌اند.
  • منطق جدید در ارزان‌ترین سطح معتبر تست شده است.
  • مرز DB/API/Event/PSP شواهد Integration یا Contract دارد.
  • Permission، Error، Retry و Recovery فقط Happy path نیستند.
  • Migration، Feature flag، Telemetry و Rollback بررسی شده‌اند.
  • Accessibility، Security و Performance بر اساس ریسک ارزیابی شده‌اند.
  • تست‌ها مستقل، تعیین‌پذیر و دارای Owner هستند.
  • Artifact شکست برای تشخیص کافی است.
  • مستند/Runbook و Dashboard هم‌زمان با رفتار تغییر کرده‌اند.
  • نتیجه در Preview یا محیط نزدیک قرارداد تولید پذیرفته شده است.

۱۵ خطای رایج در استراتژی تست وب

  1. درصد ثابت برای نسبت Unit/Integration/E2E.
  2. Coverage بالا به‌عنوان اثبات کیفیت.
  3. Mock کردن همه مرزها، از جمله DB.
  4. تکرار همان Assertion در همه لایه‌ها.
  5. E2E برای هر Edge case.
  6. Selector وابسته به CSS و DOM داخلی.
  7. Sleep ثابت به‌جای انتظار بر State.
  8. Retry پنهان برای سبزکردن Flaky test.
  9. داده و حساب مشترک میان تست‌های موازی.
  10. بازسازی Artifact پس از تست.
  11. استفاده بی‌حفاظ از داده Production.
  12. تکیه بر Scanner برای امنیت یا دسترس‌پذیری.
  13. نادیده‌گرفتن Timeout، Duplicate و Partial failure.
  14. نداشتن Production signal و Rollback.
  15. مالک‌دانستن QA برای تمام کیفیت.

جمع‌بندی: Confidence را به‌صورت یک سیستم مهندسی کنید

سبد تست خوب نه بیشترین تعداد Test، بلکه کوتاه‌ترین مسیر معتبر از تغییر تا شواهد است. Risk map و قرارداد کیفیت مشخص می‌کنند چه چیز ارزش محافظت دارد؛ Unit/Component بازخورد سریع می‌دهند؛ Integration و Contract مرزهای واقعی را می‌سنجند؛ E2E فقط Journeyهای حیاتی را متصل می‌کند؛ و Performance، Security، Accessibility و Usability خلأهای مستقل را می‌بندند. CI، Telemetry، Canary و Rollback این شواهد را به انتشار امن تبدیل می‌کنند.

سؤالات متداول تست وب‌اپلیکیشن

برای وب‌اپلیکیشن از Unit test شروع کنیم یا E2E؟

از پرریسک‌ترین Journey و ارزان‌ترین تست معتبر شروع کنید. معمولاً منطق Domain با Unit، مرز DB/API با Integration/Contract و یک Golden journey با E2E ترکیب مناسبی است. اگر محصول Legacy است، یک Characterization test پیش از Refactor ارزش زیادی دارد.

درصد مناسب Code Coverage چقدر است؟

عدد عمومی معتبری وجود ندارد. Coverage را برای کشف کد اجرا‌نشده و جلوگیری از افت ناگهانی استفاده کنید؛ Threshold را بر اساس ریسک و ماژول تعیین کنید. کیفیت Assertion، Branchهای مهم، Mutation، Defect escape و Feedback time را کنار آن ببینید.

تفاوت Unit، Integration و E2E چیست؟

Unit رفتار یک واحد با وابستگی کم را سریع می‌سنجد؛ Integration اتصال کد با یک مرز واقعی مثل DB یا Queue را می‌آزماید؛ E2E Journey را از ورودی عمومی تا خروجی نهایی روی Stack متصل بررسی می‌کند. نام‌ها کمتر از Scope، Dependency و Risk پوشش‌داده‌شده اهمیت دارند.

چگونه Flaky test را رفع کنیم؟

آن را با Owner و Issue قابل‌مشاهده قرنطینه کنید، Trace/Log و اجرای تکراری جمع کنید، و علت را در زمان، Race، Selector، داده مشترک، محیط یا Cleanup بیابید. Sleep و Retry کور درمان نیستند؛ انتظار بر State، Isolation و کنترل Dependency معمولاً راه درست‌اند.

آیا تست خودکار جای QA دستی و تست کاربر را می‌گیرد؟

خیر. Automation برای Regression تعیین‌پذیر عالی است؛ Exploratory testing رفتارهای پیش‌بینی‌نشده را می‌کاود، تست کاربردپذیری فهم و موفقیت کاربر را می‌سنجد، و ارزیابی انسانی Accessibility و Penetration test جنبه‌هایی را پوشش می‌دهند که Suite معمولی نمی‌بیند.

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

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *