یک API می‌تواند ۲۰۰ برگرداند و داده باز هم غلط باشد. سفارش «پرداخت‌شده» ثبت شده، اما موجودی دوبار کم شده است؛ Callback تکراری دو رکورد مالی ساخته؛ Migration جدید روی یک Replica اجرا نشده؛ یا دو تراکنش هم‌زمان هرکدام موجودی آخر را کافی دیده‌اند. تست UI و API ممکن است ظاهر سناریو را سبز نشان دهند، درحالی‌که Invariant اصلی شکسته است.

تست پایگاه داده (Database Testing) یعنی آزمودن Schema، Constraint، Query، Transaction، Concurrency، Migration، Backfill، سطح دسترسی، عملکرد و Restore با Oracleهای قابل‌اعتماد. هدف «تضمین مطلق» نیست؛ هدف ساختن شواهدی است که نشان دهد داده در Context و Risk تعریف‌شده، قواعد موردانتظار را حفظ می‌کند و شکست قابل‌تشخیص و بازیابی است.

مثال‌های SQL این مقاله با Syntax رایج PostgreSQL 18 نوشته شده‌اند. نوع Lock، Isolation، DDL تراکنشی، Collation، Syntax و رفتار Migration میان PostgreSQL، MySQL، SQL Server، Oracle و NoSQL تفاوت دارد؛ همیشه مستندات نسخهٔ واقعی خود را مرجع قرار دهید.

تست پایگاه داده دقیقاً چه چیزی را می‌سنجد؟

Object under test فقط جدول نیست. مسیر کامل داده می‌تواند شامل Client، API، ORM، Database، Cache، Queue، CDC، Search index و گزارش باشد. تست لایهٔ داده روی قراردادهای زیر تمرکز می‌کند:

لایه پرسش تست نمونهٔ Oracle
Schema نوع، Nullability، Default و رابطه درست‌اند؟ Schema contract / migration model
Domain هر مقدار در دامنهٔ معتبر است؟ amount ≥ ۰، currency مجاز
Entity هر رکورد هویت یکتا دارد؟ Primary/Unique key
Referential رکورد یتیم یا حذف ناسازگار نداریم؟ Foreign key / lifecycle rule
Business invariant قانون کسب‌وکار پس از هر Transition برقرار است؟ یک مرجع پرداخت، حداکثر یک اثر مالی
Transaction اثر چند تغییر با هم Commit یا Rollback می‌شود؟ State + ledger + inventory هماهنگ
Concurrency Interleavingهای مجاز Invariant را نمی‌شکنند؟ عدم Oversell یا Lost update
Evolution Migration/Backfill با نسخه‌های هم‌زمان سازگار است؟ قدیم+جدید در Rolling deploy
Recovery پس از Failure به RPO/RTO و State معتبر می‌رسیم؟ Restore + application invariants

تست پایگاه داده جای تست API یا تست سیستم را نمی‌گیرد. API مجوز و Contract بیرونی را می‌سنجد؛ تست DB مکانیزم و Invariant ذخیره‌سازی را؛ تست سیستم جریان کامل را. یک Risk مهم معمولاً به شواهد هر سه سطح نیاز دارد.

Data Integrity فقط «داده خراب نیست» نیست

یکپارچگی دامنه (Domain integrity)

مقدار با نوع و دامنه سازگار است: مبلغ منفی نیست، Currency روشن است، Status از مجموعهٔ مجاز می‌آید و Timestamp معنای زمانی مشخص دارد.

یکپارچگی موجودیت (Entity integrity)

هر Entity کلید پایدار و یکتا دارد. ایمیل، شماره همراه یا نام معمولاً Primary key خوبی نیستند چون تغییرپذیر و گاهی مشترک‌اند.

یکپارچگی ارجاعی (Referential integrity)

Order به Customer موجود اشاره می‌کند و سیاست حذف روشن است: RESTRICT، CASCADE، SET NULL یا Soft delete. Cascade باید Outcome کسب‌وکار باشد، نه Default بدون تحلیل.

Invariant کسب‌وکار

Constraintهای ساده کافی نیستند. «مجموع Ledger صفر می‌شود»، «State از Paid به Awaiting برنمی‌گردد» یا «هر Callback فقط یک‌بار اثر دارد» ممکن است چند جدول، Event و Transaction را درگیر کند.

یکپارچگی زمانی و توزیع‌شده

کدام Event جدیدتر است؟ آیا Replica یا Search index هنوز عقب است؟ آیا Delete پس از CDC در Consumer اعمال شده؟ Consistency مدل سیستم باید در Oracle منعکس شود؛ تأخیر مجاز با SLO مشخص شود، نه با Sleep دلخواه.

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

اصل معنای عملی دام رایج
Atomicity اثر Transaction کامل Commit یا Rollback می‌شود سرویس/Queue بیرون از Transaction خودکار Atomic نیست
Consistency قواعد اعلام‌شدهٔ DB از State معتبر به State معتبر می‌روند DB قانون کسب‌وکارِ تعریف‌نشده را نمی‌شناسد
Isolation تراکنش‌های هم‌زمان طبق سطح و پیاده‌سازی تعامل می‌کنند نام یک Isolation level در همهٔ موتور‌ها رفتار یکسان ندارد
Durability Commit پذیرفته‌شده طبق تنظیمات Durability باقی می‌ماند Ack، Replica، Storage و Failure mode باید آزموده شوند

ACID مساوی «نرم‌افزار درست است» نیست. اگر برنامه مبلغ اشتباه را در یک Transaction اتمیک ثبت کند، داده به‌شکل کاملاً اتمیک غلط می‌شود. Test باید Invariant کسب‌وکار را مستقل از مسیر پیاده‌سازی ارزیابی کند.

Constraintها؛ اولین خط دفاع داده

مستندات رسمی Constraints در PostgreSQL رفتار CHECK، NOT NULL، UNIQUE، PRIMARY KEY و FOREIGN KEY را توضیح می‌دهد. نمونهٔ زیر صرفاً PostgreSQL است:

CREATE TABLE orders (
  id             bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  customer_id    bigint NOT NULL REFERENCES customers(id),
  merchant_ref   text NOT NULL UNIQUE,
  amount         numeric(18,2) NOT NULL CHECK (amount >= 0),
  currency       char(3) NOT NULL CHECK (currency IN ('IRR', 'IRT')),
  status         text NOT NULL CHECK (
                   status IN ('awaiting_payment', 'paid', 'cancelled', 'refunded')
                 ),
  created_at     timestamptz NOT NULL DEFAULT now()
);

برای هر Constraint دو خانواده تست بسازید

  • Positive: دادهٔ معتبر پذیرفته و دقیق ذخیره شود.
  • Negative: NULL، Duplicate، FK ناموجود، مقدار مرزی و Status نامعتبر با SQLSTATE/خطای موردانتظار رد شوند.

فقط «خطا رخ داد» کافی نیست؛ بعد از Failure بررسی کنید Transaction چه وضعی دارد و هیچ اثر نیمه‌کاره‌ای باقی نمانده است. برای تست خطای عمدی، از Database ایزوله و Rollback/Cleanup قطعی استفاده کنید.

محدودیت CHECK را بشناسید

در PostgreSQL، CHECK برای شرط همان Row طراحی شده و ارجاع قابل‌اعتماد به دادهٔ جدول دیگر را پشتیبانی نمی‌کند. قواعد Cross-row یا Cross-table به Unique/FK، Transaction logic، Trigger حساب‌شده یا طراحی دیگر نیاز دارند. Trigger نیز کد است: ترتیب اجرا، Bulk operation، Recursion، خطا و Migration آن باید تست شود.

Oracle مستقل؛ همان Query تولید را دوباره ننویسید

اگر Test همان ORM/Query تولید را برای محاسبهٔ Expected استفاده کند، یک Bug مشترک می‌تواند هر دو را سبز کند. Oracleهای قوی‌تر:

  • Invariant دامنه یا Ledger که مستقل محاسبه می‌شود؛
  • Contract Schema تحت Version control؛
  • Event/State history برای Transition؛
  • Reference implementation کوچک و روشن؛
  • Constraint واقعی DB برای قواعد ساختاری؛
  • مقایسهٔ Source/Target با Count، Aggregate و Sample کنترل‌شده؛
  • Metamorphic relation: تغییر ورودی باید رابطهٔ مشخصی در خروجی بسازد.

Queryهای تشخیصی می‌توانند Drift را آشکار کنند، اما خودشان Repair نیستند:

-- سفارش یتیم؛ انتظار صفر Row
SELECT o.id
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id
WHERE c.id IS NULL;

-- مرجع پرداخت تکراری؛ انتظار صفر Row
SELECT merchant_ref, count(*)
FROM orders
GROUP BY merchant_ref
HAVING count(*) > 1;

در Dataset بزرگ، Full scan ممکن است پرهزینه باشد. Scope، Index و زمان اجرا را در محیط امن طراحی کنید؛ Diagnostic تولید نباید بدون مجوز و Budget منابع اجرا شود.

تست CRUD کافی نیست؛ Mapping را بشکنید

داده حالت‌های ضروری خطر
NULL/Empty NULL، رشته خالی، فاصله، نبود Field معنای متفاوت در API/ORM/DB
Money ۰، مرز، Refund، Currency، rounding Float و واحد ریال/تومان
Time UTC، Zone، مرز روز، expiry، clock skew Timestamp بدون Zone و Offset ثابت
Unicode ی/ی، ک/ک، نیم‌فاصله، Emoji، ترکیب حروف Duplicate و جست‌وجوی ناسازگار
Digits ۱۲۳، ۱۲۳ و ۱۲۳ Validation/Normalization متفاوت
Identifier +۹۸، صفر ابتدایی، حروف بزرگ/کوچک ذخیره به‌عنوان عدد یا Collation غلط
Large value حد طول، Payload بزرگ، عدد بیشینه Truncation یا Overflow
Order Sort پایدار با Tie-breaker Pagination تکراری/گمشده

پول را با Float مدل نکنید

Amount، Currency و واحد را صریح نگه دارید. IRR و IRT را با نام مبهم «price» مخلوط نکنید. Rounding برای مالیات، تخفیف و Refund باید Rule مالک داشته باشد و با Boundary test پوشش داده شود.

Timezone را به Offset ثابت تقلیل ندهید

Instant را با UTC/نوع دارای Zone نگه دارید و نمایش محلی را در مرز مناسب انجام دهید. قانون منطقهٔ زمانی می‌تواند تغییر کند؛ Offset ثابت جای IANA timezone نیست. تاریخ جلالی می‌تواند ورودی/نمایش دامنه باشد، اما تبدیل، مرز روز و Round-trip باید آزموده شود.

Normalization فارسی باید قرارداد باشد

قبل از Unique یا Search روشن کنید «ی» فارسی و عربی، «ک/ک»، اعداد فارسی/عربی/لاتین و نیم‌فاصله چگونه Canonical می‌شوند. Normalization پنهان ممکن است دو هویت متفاوت را یکی کند؛ قرارداد را برای هر Field، نه کل سیستم، تعیین کنید.

تست Transaction و Rollback

یک سناریوی Transactional باید حداقل سه Outcome داشته باشد:

  1. همهٔ تغییرها موفق و Commit می‌شوند؛
  2. خطا در وسط کار باعث Rollback همهٔ اثرهای درون Transaction می‌شود؛
  3. خطای پس از Commit با Retry، اثر Duplicate نمی‌سازد.

مثال پرداخت:

  • Order به Paid می‌رود؛
  • Payment ledger درج می‌شود؛
  • Inventory/fulfilment یک‌بار اثر می‌گیرد؛
  • Outbox event در همان مرز معتبر ایجاد می‌شود؛
  • اگر Client پاسخ را نگیرد و Retry کند، Idempotency key نتیجه را تکرار نمی‌کند.

Queue یا سرویس ثالث معمولاً داخل Transaction پایگاه داده نیست. Outbox، Saga یا Compensation هرکدام Contract و Failure mode مخصوص دارند؛ «BEGIN/COMMIT» محلی Atomicity کل سیستم توزیع‌شده را تضمین نمی‌کند.

تست Concurrency و Isolation

مستندات جاری Transaction Isolation در PostgreSQL ۱۸ تفاوت Read Committed، Repeatable Read و Serializable و لزوم Retry برای Serialization failure را شرح می‌دهد. Test باید سطح واقعی Connection، Pool و Transaction را ثبت کند؛ Default را حدس نزنید.

سناریوهای ضروری هم‌زمانی

سناریو Interleaving Invariant
Lost update دو Worker یک نسخه را می‌خوانند و می‌نویسند Version/lock/CAS از overwrite پنهان جلوگیری کند
Oversell دو خرید آخرین موجودی را هم‌زمان رزرو می‌کنند موجودی منفی یا دو رزرو موفق نداریم
Duplicate callback دو Request با Event ID یکسان یک اثر مالی و پاسخ Idempotent
Write skew هر Transaction شرط مشترک را معتبر می‌بیند حداقل/حداکثر Cross-row حفظ شود
Deadlock Lock منابع با ترتیب معکوس یکی Retry/Fail کنترل‌شده؛ اثر نیمه‌کاره صفر
Serialization retry SQLSTATE 40001 کل Transaction با Backoff/limit دوباره اجرا شود

هماهنگی Threadها را Deterministic کنید

به‌جای امید به Race، از Barrier/Latch تست استفاده کنید:

  1. Transaction A و B State پایه را می‌خوانند.
  2. هر دو پشت Barrier متوقف می‌شوند.
  3. A تغییر/Commit می‌کند.
  4. B طبق برنامه تغییر/Commit یا Retry می‌کند.
  5. State نهایی و هر دو نتیجه ثبت می‌شوند.

Sleep ثابت Test را کند و Flaky می‌کند. اگر Failure به زمان‌بندی خاص وابسته است، روش‌های مقالهٔ بازتولید باگ متناوب برای Correlation و آزمایش فرضیه مفیدند.

Lock و Deadlock را مشاهده کنید

PostgreSQL انواع Lock و نمای pg_locks را در مستندات Explicit Locking شرح می‌دهد. Test باید Lock wait، Timeout، Deadlock victim و Recovery را ببیند؛ صرف Pass شدن بعد از ۳۰ ثانیه عملکرد قابل‌قبول را اثبات نمی‌کند.

Migration تست‌نشده یکی از پرریسک‌ترین تغییرهاست

Migration فقط «SQL اجرا شد» نیست. باید Fresh install، Upgrade از نسخه‌های پشتیبانی‌شده، Rolling deploy، Retry، Lock impact، Backfill و Recovery را پوشش دهد.

الگوی Expand → Migrate → Contract

  1. Expand: ستون/جدول جدید را به‌شکل سازگار اضافه کنید؛ در صورت لزوم Nullable یا با Default امن.
  2. Deploy compatible app: نسخهٔ جدید با Schema قدیم/جدید در Window تعیین‌شده کار کند.
  3. Migrate data: Backfill قطعه‌ای، Idempotent و قابل Resume اجرا شود.
  4. Verify: Count، Null، Aggregate، Sample و Invariant مستقل مقایسه شود.
  5. Enforce: Constraint/NOT NULL پس از پاکی داده اعمال و Validate شود.
  6. Contract: مسیر/ستون قدیمی فقط بعد از خروج نسخه‌های قدیمی حذف شود.

نوع Lock هر DDL را روی نسخهٔ واقعی موتور بررسی کنید. برخی ALTERها جدول را اسکن، Rewrite یا Write را مسدود می‌کنند. Dry run روی Dataset کوچک برای برآورد Production کافی نیست؛ Cardinality، Data distribution و concurrent traffic را مدل کنید.

Migration اجراشده را ویرایش نکنید

ابزارهای Migration معمولاً Schema history و checksum نگه می‌دارند. مستندات جاری Flyway Validate اختلاف نام/نوع/checksum و Migrationهای گمشده یا Pending را گزارش می‌کند. اصل کلی مستقل از ابزار است: Script اعمال‌شده Immutable باشد؛ اصلاح با Migration جدید و Audit trail انجام شود.

Rollback همیشه ممکن نیست

DROP، تبدیل Lossy یا Backfill اشتباه ممکن است Down migration امن نداشته باشد. برای هر تغییر یکی از مسیرهای زیر را آزمایش کنید:

  • Rollback واقعی و بدون از‌دست‌دادن داده؛
  • Roll-forward اصلاحی؛
  • Feature rollback با Schema سازگار باقی‌مانده؛
  • Restore/PITR با RPO/RTO پذیرفته‌شده.

«Rollback script وجود دارد» شواهد کافی نیست؛ آن را روی Clone ایزوله با داده و نسخهٔ قبلی اجرا و Outcome برنامه را بررسی کنید.

Backfill و Data migration را چگونه تست کنیم؟

Backfill خوب باید قابل Resume، Idempotent، Observable و Rate-limited باشد. Test matrix:

  • Dataset خالی، کوچک، بزرگ و Skewed؛
  • Rowهای معتبر، Null، Legacy و ناسازگار؛
  • Crash وسط Batch و Resume؛
  • اجرای دوباره بدون Duplicate/تغییر اضافه؛
  • نوشتن هم‌زمان برنامه در طول Backfill؛
  • Batch boundary، آخرین Row و Pagination پایدار؛
  • Throttle، Lock wait و Replica lag؛
  • مقایسهٔ Source/Target با Count، Aggregate و Exception report؛
  • Retention و حذف دادهٔ موقت پس از Verify.

استفاده از Snapshot خام Production راه کوتاه ولی پرخطر است. Synthetic، Subset و Masking هرکدام محدودیت بازنمایی دارند؛ راهنمای مدیریت داده تست تفاوت Masking، Pseudonymization و دادهٔ مصنوعی را توضیح می‌دهد.

تست امنیت لایهٔ پایگاه داده

این کار باید در محدودهٔ مجاز و ترجیحاً محیط غیرعملیاتی انجام شود. Checklist پایه:

  • Queryها Parameterized باشند؛ ورودی با Concatenation به SQL تبدیل نشود.
  • نام Column/Table/Sort از Allowlist کد بیاید، نه ورودی آزاد.
  • Application role حداقل SELECT/INSERT/UPDATE/DELETE موردنیاز را داشته باشد.
  • Migration role، runtime role، read-only analytics و DBA از هم جدا باشند.
  • هر Tenant فقط Row/Schema مجاز را ببیند؛ مجوز در API و DB/Query هر دو آزموده شود.
  • Secret در Connection string، Log، Dump، Artifact یا CI output افشا نشود.
  • Backup، Replica، Export و ابزار BI همان کنترل دادهٔ اصلی را داشته باشند.
  • Error پاسخ کاربر Schema/Query/credential را لو ندهد؛ Evidence فنی در محل امن بماند.

OWASP SQL Injection Prevention Cheat Sheet Parameterized query، Allowlist برای بخش‌های غیرقابل Bind و Least privilege را توصیه می‌کند. Stored procedure خودکار امن نیست؛ Dynamic SQL داخل آن نیز باید کنترل شود. برای چارچوب گسترده‌تر، راهنمای OWASP Top ۱۰:۲۰۲۵ را ببینید.

تست Query و عملکرد پایگاه داده

Query روی ده Row سریع است و روی ده میلیون Row با توزیع واقعی ممکن است Plan دیگری بگیرد. Test data باید Cardinality، Skew، Selectivity، Null rate، تاریخچه و Relation size نماینده داشته باشد.

چه چیزی را اندازه بگیریم؟

  • p50/p95/p99 latency به تفکیک Query/Journey؛
  • Rows estimated در برابر actual؛
  • Rows scanned/returned و Buffer hit/read؛
  • Lock wait، deadlock، timeout و connection-pool queue؛
  • CPU، I/O، memory spill، temp space و WAL/replication lag؛
  • Throughput/Goodput در برابر Error/Retry؛
  • Plan change پس از Migration، Index یا Statistics update.

مستندات EXPLAIN در PostgreSQL تفاوت Plan تخمینی و EXPLAIN ANALYZE را توضیح می‌دهد. ANALYZE Query را واقعاً اجرا می‌کند. برای INSERT/UPDATE/DELETE روی Production به‌قصد «فقط دیدن Plan» استفاده نکنید. نمونهٔ رسمی نیز DML را در Transaction اجرا و سپس Rollback می‌کند، اما حتی Rollback می‌تواند Lock، Trigger، Sequence، I/O یا اثر بیرونی داشته باشد؛ محیط ایزوله امن‌تر است.

Threshold جهانی مانند «CPU زیر ۸۰٪» کافی نیست. Load model، SLO، Dataset، Cache state، Window و Stop condition را در برنامه تست عملکرد تعریف کنید.

Backup زمانی ارزش دارد که Restore شده باشد

Job سبز Backup فقط می‌گوید فرایند چیزی نوشته است. Drill بازیابی باید نشان دهد فایل/Archive قابل‌خواندن، کلید و وابستگی حاضر، Sequence کامل و برنامه پس از Restore قابل‌استفاده است.

سناریوهای Recovery

  • Full restore روی محیط ایزوله؛
  • Point-in-time recovery پیش از تغییر مخرب؛
  • Replica promotion و Client reconnection؛
  • از‌دست‌رفتن یک Zone/Node؛
  • Backup ناقص یا WAL segment گمشده؛
  • Restore نسخهٔ قدیمی با App/Schema سازگار؛
  • اعتبارسنجی Permission، Extension، Job، Sequence و Object storage؛
  • Application smoke + invariant queries پس از بازیابی.

PostgreSQL در مستندات Continuous Archiving و PITR ترکیب Base backup و WAL و امکان توقف در نقطهٔ زمانی را توضیح می‌دهد. جزئیات به نسخه و معماری شما وابسته است. RPO مقدار دادهٔ قابل‌از‌دست‌دادن و RTO زمان قابل‌قبول بازگشت است؛ هر دو باید با Stopwatch و Evidence واقعی سنجیده شوند.

هر تست را کجا اجرا کنیم؟

لایه نمونه تست فرکانس
Static/Migration Lint، naming، checksum، forbidden operation هر Commit
Ephemeral DB Fresh migrate، constraint، repository، rollback هر PR
Service integration API→DB mapping، transaction، outbox هر PR/merge
Concurrency Barrier-based race، retry، deadlock PR حیاتی + nightly
Representative dataset Backfill، plan، lock impact، large data Release/nightly
Restore/DR Backup restore، PITR، failover برنامهٔ دوره‌ای مصوب
Production read-only Drift/invariant monitor مجاز پایش با Budget

در Pipeline تست مداوم، Fast lane باید Feedback سریع بدهد و آزمون‌های حجیم/Restore در Lane جدا با Gate و Owner روشن اجرا شوند. نتیجهٔ Nightly نباید Release پرریسک را بی‌مالک رها کند.

مثال عملی: پرداخت تکراری در فروشگاه ایرانی

Invariantها

  • هر gateway_event_id حداکثر یک Payment effect دارد.
  • Retry همان Event همان Outcome را برمی‌گرداند.
  • Order پس از Paid به AwaitingPayment برنمی‌گردد.
  • Amount با واحد IRR/IRT و Scale تعریف‌شده ذخیره می‌شود.
  • Inventory و Ledger اثر Duplicate نمی‌گیرند.
  • Outbox event با Commit همان Transaction هم‌مرز است.

طرح تست

  1. Order و Inventory ایزوله با شناسهٔ Test run بسازید.
  2. دو Worker را با Event ID یکسان تا پشت Barrier ببرید.
  3. هر دو هم‌زمان Transaction را ادامه دهند.
  4. Response، SQLSTATE، Retry count، trace ID و زمان Commit ثبت شود.
  5. Payment count، Order state، Inventory delta، Ledger و Outbox مستقل Query شوند.
  6. Test دوباره اجرا شود تا Idempotency روی State موجود سنجیده شود.
  7. Cleanup فقط رکوردهای همان Namespace را حذف کند.

Expected

یک Worker اثر را ایجاد می‌کند؛ دیگری نتیجهٔ موجود یا Conflict قابل‌مدیریت می‌گیرد. State نهایی دقیقاً یک Payment، یک Inventory delta و یک Outbox event دارد. اگر Serializable به SQLSTATE ۴۰۰۰۱ برسد، کل Transaction در محدودهٔ Retry دوباره اجرا می‌شود، نه فقط آخرین Statement.

Migration مرتبط

اگر gateway_event_id جدید است، ابتدا Nullable/سازگار اضافه شود؛ App جدید آن را بنویسد؛ Backfill رکوردهای قابل‌اعتماد را پر کند؛ Duplicate/NULL گزارش شوند؛ سپس Unique/NOT NULL در مسیر سازگار Enforce شود. نسخهٔ قدیمی App نباید در Rolling window با Schema جدید بشکند.

قالب Test Case پایگاه داده

ID / risk: DB-CONC-PAY-01 / duplicate financial effect
DB engine/version: PostgreSQL 18.x
Schema/migration version: …
Build/config/isolation: …
Preconditions: isolated tenant, seeded order, pool settings …
Invariant: one event → one financial effect
Data: values, boundaries, Unicode/time/currency …
Interleaving: barrier steps for session A/B
Action: SQL/API/migration command in non-production
Expected: result, SQLSTATE, row counts, state transition …
Evidence: sanitized query/result, trace, plan, lock/metric …
Cleanup: transaction/namespace reset
Owner / retest: …

در گزارش Failure، Secret و دادهٔ واقعی را Redact کنید؛ Build، Schema version، isolation و Interleaving برای بازتولید ضروری‌اند. قالب عمومی گزارش باگ حرفه‌ای به Expected/Actual و Evidence ساختار می‌دهد.

متریک‌های مفید برای کیفیت لایهٔ داده

متریک پرسش تصمیم هشدار
Invariant violations کدام قانون، نسخه و مسیر می‌شکند؟ صفر ممکن است از Monitor ناقص باشد
Migration duration/lock wait Window و Rollout امن‌اند؟ دادهٔ کوچک CI نماینده نیست
Serialization/deadlock retry Concurrency design پایدار است؟ Retry بی‌حد مشکل را پنهان می‌کند
Query p95/p99 by shape کدام Query/SLO نیاز به اقدام دارد؟ میانگین Tail را مخفی می‌کند
Replica/CDC lag Consistency window رعایت می‌شود؟ Metric بدون user impact کافی نیست
Restore success + RPO/RTO واقعاً قابل‌بازیابی هستیم؟ Backup success جای Restore نیست
Test isolation failures آیا Suite دادهٔ مشترک/Flaky دارد؟ نباید با Defect محصول مخلوط شود

اشتباه‌های رایج در تست پایگاه داده

  • فقط CRUD: Concurrency، Migration، Restore و Invariant پوشش ندارند.
  • ACID = صحت کسب‌وکار: قانون غلط می‌تواند اتمیک Commit شود.
  • کپی Production: حریم خصوصی، Retention و Blast radius نادیده گرفته می‌شود.
  • اعتماد به Auto-increment دقیق: Sequence می‌تواند Gap داشته باشد و در PostgreSQL تغییر Sequence لزوماً با Rollback برنمی‌گردد.
  • Sleep برای Race: Test کند و غیرقطعی می‌شود.
  • SELECT بدون ORDER BY: ترتیب پایدار فرض می‌شود.
  • ویرایش Migration اعمال‌شده: محیط‌ها Drift و بازسازی غیرقابل‌اعتماد می‌شوند.
  • Rollback نمایشی: Down script بدون داده/نسخهٔ واقعی آزموده نشده است.
  • Disable constraint برای سرعت: Test مسیری را می‌سنجد که Production نباید داشته باشد.
  • EXPLAIN ANALYZE بی‌محابا: Query عملاً اجرا و منابع/Lock مصرف می‌کند.
  • Backup سبز: Restore، کلید، WAL و App smoke آزموده نشده‌اند.
  • Cleanup گسترده: TRUNCATE یا Delete بدون Namespace دادهٔ Test دیگران را خراب می‌کند.
  • تمام اختلاف‌ها = Defect: Oracle، Replication window و Expected consistency بررسی نشده‌اند.

چک‌لیست نهایی تست پایگاه داده

  • DB engine/version، Schema version و Isolation ثبت شده‌اند.
  • Invariantهای دامنه، موجودیت، ارجاع، کسب‌وکار و زمان نوشته شده‌اند.
  • Constraintها Positive و Negative test دارند.
  • NULL، Empty، Boundary، Money، Timezone و Unicode فارسی پوشش دارند.
  • Transaction در Success، Mid-failure و Retry آزموده شده است.
  • Raceها با Barrier کنترل‌شده و State نهایی مستقل بررسی می‌شوند.
  • Migration روی Fresh، Upgrade، Rolling compatibility و Retry اجرا شده است.
  • Backfill Idempotent، Resumeable، Observable و قابل Verify است.
  • Runtime/Migration/Analytics role حداقل دسترسی لازم دارند.
  • Query plan روی توزیع دادهٔ نماینده و محیط امن بررسی شده است.
  • Backup واقعاً Restore و RPO/RTO اندازه‌گیری شده‌اند.
  • تست‌ها ایزوله‌اند و Cleanup فقط Namespace خود را لمس می‌کند.
  • Evidence Sanitized و قابل‌ردیابی به Build/Schema است.

سوالات متداول تست پایگاه داده

تست پایگاه داده با تست API چه تفاوتی دارد؟

تست API Contract، مجوز و Outcome قابل‌مشاهدهٔ سرویس را می‌سنجد؛ تست پایگاه داده Schema، Constraint، Mapping، Transaction، Concurrency، Migration و Recovery را. برای جریان حیاتی، هر دو لازم‌اند و نباید Test به جزئیات DB آن‌قدر وابسته شود که Refactor بی‌خطر را بشکند.

آیا ACID یکپارچگی داده را تضمین می‌کند؟

ACID رفتار Transaction را طبق قواعد اعلام‌شدهٔ DB توصیف می‌کند، اما قانون کسب‌وکاری که تعریف یا درست پیاده نشده خودکار تضمین نمی‌شود. Isolation و Durability نیز به موتور، نسخه، تنظیمات و Failure mode وابسته‌اند.

چطور Race condition پایگاه داده را تست کنیم؟

دو یا چند Session را با Barrier در نقاط مشخص هماهنگ کنید، Isolation/lock را ثبت و Interleaving هدفمند بسازید. سپس Result هر Session، SQLSTATE/Retry و Invariant State نهایی را مستقل بررسی کنید؛ Sleep تصادفی شواهد ضعیف‌تری می‌دهد.

آیا می‌توان از دادهٔ Production برای تست استفاده کرد؟

پیش‌فرض امن، Synthetic یا Subset کنترل‌شده است. اگر دادهٔ عملیاتی واقعاً لازم باشد، مجوز، حداقل‌سازی، Masking/Pseudonymization، کنترل دسترسی، Retention، محیط ایزوله و ریسک بازشناسایی باید بررسی شوند. Copy خام و نامحدود انتخاب قابل‌قبولی نیست.

آیا داشتن Backup یعنی Disaster Recovery آماده است؟

خیر. باید Restore یا PITR روی محیط ایزوله اجرا، زمان واقعی سنجیده و Schema، Permission، Sequence، داده و رفتار برنامه پس از بازیابی اعتبارسنجی شود. Backupی که کلید، WAL یا وابستگی‌اش موجود نیست قابل اتکا نیست.

منابع و یادداشت بازبینی

رفتار Constraint، Isolation/Retry، Lock، EXPLAIN و PITR با مستندات جاری PostgreSQL ۱۸؛ Migration checksum با مستندات Flyway؛ و Parameterization/Least privilege با OWASP تطبیق داده شده است. آخرین بازبینی محتوایی: ۱۵ مرداد ۱۴۰۵. SQL نمونه را پیش از استفاده با موتور، نسخه، Driver و سیاست عملیاتی خود تطبیق دهید؛ اجرای DDL/DML تشخیصی روی Production فقط با مجوز، Backup/Recovery و کنترل اثر مجاز است.

جمع‌بندی: تست پایگاه داده با شمارش جدول‌ها یا سبزشدن CRUD کامل نمی‌شود. Invariant را بنویسید، آن را تا حد مناسب در Constraint قرار دهید، Transaction و Race را با Interleaving کنترل‌شده بیازمایید، Migration و Backfill را بخشی از محصول بدانید، دسترسی را حداقلی کنید و Backup را Restore کنید. این رویکرد دادهٔ «ظاهراً درست» را به State قابل‌اعتماد و قابل‌بازیابی نزدیک می‌کند.

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