ده تست API همگی سبز بودند: Status مورد انتظار برگشت و Body نیز Schema را پاس کرد. بااینحال، Retry همان سفارش را دوبار ثبت کرده بود، پاسخ Conflict یک برداشت مالی پنهان داشت، Timeout بعد از Commit رخ داده بود، Cache داده Tenant دیگری را نشان میداد و Pagination یک رکورد را دو بار تحویل میداد. «۱۰ از ۱۰ Pass» در واقع هفت False Pass داشت.
مشکل از Client یا Syntax تست نبود؛ Oracle فقط به لایه Request/Response نگاه میکرد. یک API Test Harness معتبر باید علاوه بر Status و Schema، حالت دامنه، اثرهای جانبی، نبودن اثر ممنوع، امنیت Object/Tenant، رفتار مجموعه، Retry/Concurrency و شواهد قابلردیابی را بسنجد.
این راهنما معماری چنین Harnessی را برای APIهای HTTP/JSON توضیح میدهد: Contract→State/Data→Request→Oracle چندلایه→Failure/Retry/Concurrency→Evidence→CI. مثالها ساختگیاند، ولی قراردادها و روش آزمایش قابلاجرا هستند.
پاسخ کوتاه: API Test Harness پیشرفته چیست؟
API Test Harness مجموعهای از Client، Fixture/Data builder، Dependency controller، Clock/Identity، Assertion/Oracle، State probe، Evidence collector و Runner است که یک ریسک API را به Verdict بازتولیدپذیر تبدیل میکند. ابزار ارسال Request فقط یکی از اجزای آن است.
Risk / Requirement
-> precondition + identity + data
-> request / sequence / concurrency
-> transport + HTTP + schema assertions
-> domain + state + side-effect + security oracle
-> trace / log / datastore evidence
-> cleanup + verdict + CI gate
مرز این مقاله با راهنماهای نزدیک
راهنمای جامع تست API مالک مبانی، انواع تست و چکلیست عمومی است. این مقاله مرحله بعد را مالک است: طراحی Harness و Oracle برای کشف خطاهایی که پشت Response ظاهراً صحیح پنهان میشوند.
برای اجرای پروژهمحور با Collection، Environment و CLI به آموزش تست API با Postman مراجعه کنید. در اینجا Postman، REST Assured، Supertest، Playwright APIRequest، Karate یا Client سفارشی را رتبهبندی نمیکنیم؛ معیار، Evidence قابلاعتماد است.
تست قراردادی با Pact سازگاری Consumer/Provider را عمیق میکند و استراتژی تست میکروسرویسها سبد چندسرویسی/Event/E2E را میسازد. Harness این مقاله روی رفتار HTTP API و Oracle چندلایه یک سرویس یا Slice کنترلشده تمرکز دارد.
Request/Response تنها سطح مشاهده است، نه حقیقت
Response میتواند از نظر Syntax صحیح باشد و در همان لحظه Domain را خراب کند. مثالها:
201برگشته، اما Order و Ledger مبلغ متفاوت دارند؛409برگشته، اما قبل از Conflict موجودی کم شده است؛504برگشته، اما عملیات Commit شده و Client دوباره میفرستد؛- Schema معتبر است، اما مقدار
amountبه تومان و Ledger به ریال تفسیر شده؛ 404برای User غریبه درست است، ولی Audit log شماره ملی را افشا میکند؛- دو Page جدا معتبرند، اما اجتماع آنها Duplicate و Gap دارد.
پس Verdict باید از چند شاهد مستقل ساخته شود. «Status=۲۰۰» نه بهمعنای موفقیت کسبوکار است و نه الزاماً در همه Contextها موفقیت تست؛ حتی مشاهدهپذیری استاندارد نیز تفسیر Status را به Context عملیات وابسته میداند.
Test Contract Card را پیش از کدنویسی بسازید
هر Test باید قرارداد تصمیم داشته باشد، نه فقط مراحل اجرا. قالب زیر را برای سناریوی پرریسک پر کنید:
test_id: order-create-idempotency-v3
risk: retry after ambiguous timeout creates duplicate charge
api/version/operation: checkout-api@sha256:... POST /v3/orders
actor/tenant: customer-A / tenant-17
pre_state: cart=open; ledger_debits=0; order_count=0
request: media type + headers + exact bytes + idempotency key
stimulus: first call; connection loss; same-key retry
response_oracle: status + headers + schema + problem type
domain_oracle: one order; canonical amount=1250000 IRR
side_effect_oracle: one debit; one outbox event; no duplicate SMS
security_oracle: tenant-A only; no PII/secret in evidence
timing/concurrency: 2 attempts within 5s; same payload
evidence: run/trace/request/order/ledger/event IDs + versions
cleanup: namespace=run-8f2; TTL + verified deletion
verdict: pass/fail/inconclusive + exact failed oracle
Requirement، ریسک و Oracle را از هم جدا کنید
- Requirement: یک Idempotency key یک اثر کسبوکار بسازد.
- Risk: Timeout مبهم، Retry و برداشت دوباره ایجاد کند.
- Stimulus: اتصال پس از ارسال Body و پیش از خواندن Response قطع شود.
- Oracle: تعداد Order/Debit/Event و پاسخ Retry با قرارداد برابر باشد.
- Evidence: State واقعی و Trace هر Attempt، نه Screenshot پنجره Client.
Test object و مرز اجرا را دقیق Freeze کنید
عبارت «API نسخه تست» برای بازتولید کافی نیست. Run manifest حداقل این موارد را نگه دارد:
| بُعد | نمونه Evidence | چرا مهم است؟ |
|---|---|---|
| Service artifact | image digest + commit | نسخه واقعی رفتار |
| API description | OpenAPI digest/version | قرارداد مورد انتظار |
| Config/feature flags | sanitized snapshot | شاخه رفتار |
| Environment | base URL/region/topology | Proxy/Cache/Dependency |
| Dependencies | real/fake/sandbox + version | فهم Fidelity |
| Data | dataset version/seed/namespace | State قابلتکرار |
| Identity | role/tenant/token profile | Authorization boundary |
| Harness | runner/client/schema version | تغییر ابزار و Parser |
| Time | UTC start/clock mode | TTL، Expiry و Race |
اگر Artifact، Dependency یا Config نامعلوم باشد، نتیجه درباره «API» کلی نیست؛ فقط درباره محیط نامعلومی است که در آن اجرا شده. Verdict چنین Runی باید inconclusive یا invalid باشد، نه Pass.
معماری Harness در هفت جزء
- Contract loader: OpenAPI/Schema/Examples و Policy نسخهدار را میخواند.
- Data/identity builder: Actor، Tenant، Object و State قابلردیابی میسازد.
- Client: Bytes، Header، Redirect، Retry و Timeout را کنترل میکند.
- Dependency controller: Real container، Fake، Simulator یا Sandbox را آماده میکند.
- Oracle engine: Response، Domain، State، Side effect، Security و Collection را میسنجد.
- Evidence collector: Request/Response redacted، Trace، State snapshot و Versionها را پیوند میدهد.
- Runner/Gate: Isolation، Parallelism، Retry policy، Report و Exit code را مدیریت میکند.
این اجزا میتوانند در یک Repository ساده باشند؛ هدف، Micro-framework پیچیده نیست. مرز مسئولیت مانع میشود Script تست، داده، Network retry و Business assertion را در یک Function مبهم مخلوط کند.
Oracle چندلایه: Verdict از چه ساخته میشود؟
| لایه | پرسش | نمونه Failure پنهان |
|---|---|---|
| Transport | درخواست/Body کامل منتقل شد؟ | قطع پس از Header |
| HTTP semantics | Method/Status/Header/Cache درستاند؟ | Retry ناامن POST |
| Representation | Media type و Schema/Encoding درستاند؟ | عدد بهصورت String مبهم |
| Domain | مقدار و قانون کسبوکار درست است؟ | ریال/تومان اشتباه |
| State | Transition و Version درست است؟ | Paid→Pending |
| Side effect | اثر لازم دقیقاً رخ داد و اثر ممنوع نه؟ | دو Debit |
| Security | Actor/Object/Tenant/Data boundary حفظ شد؟ | Cross-tenant Cache |
| Collection | Pagination/Filter/Sort یک مجموعه درست ساخت؟ | Gap/Duplicate |
| Evidence | نتیجه قابلنسبت و تشخیص است؟ | Trace ناقص |
همه Testها به همه Probeها نیاز ندارند
GET عمومی شاید State probe نخواهد؛ POST مالی قطعاً میخواهد. برای هر ریسک کوچکترین مجموعه شواهدی را انتخاب کنید که False Pass را مهار کند. Probe زیاد نیز میتواند Test را کند، شکننده یا بیشازحد وابسته به Implementation کند.
OpenAPI قرارداد Interface است، نه Oracle کامل
OpenAPI Specification 3.2.0 نسخه منتشرشده فعلی است و توصیف زبانناوابسته HTTP API را تعریف میکند. Description میتواند Operation، Parameter، Security scheme، Media type، Schema، Example و Response را به Tool بدهد؛ اما خودش اثبات نمیکند Ledger صحیح شده، Authorization در Object level برقرار است یا Side effect فقط یکبار رخ داده است.
Schema validation چه چیزی را میگیرد؟
- Required/optional، type، enum، range و pattern؛
- Shapeهای متفاوت Success/Error؛
- ناهمخوانی Media type و Representation؛
- فیلد اضافه/حذفشده بر اساس Policy؛
- نمونههای نامعتبر Description یا Response.
JSON Schema Draft 2020-12 Core و Validation vocabulary را جدا میکند و رفتارهایی مانند Array/Tuple، dynamic reference و unevaluated items را دقیقتر میسازد. Dialect و Validator version را Freeze کنید؛ اینکه Tool کدام Keyword یا format را Assertion میگیرد ممکن است متفاوت باشد.
Contract Drift را دوطرفه بسنجید
- Description را Lint و Parse کنید؛
- Requestهای Harness را در برابر Operation/Schema اعتبارسنجی کنید؛
- Responseهای واقعی را با Description تطبیق دهید؛
- Examples را به Test Case تبدیل و stale بودنشان را آشکار کنید؛
- نسخه deployed و Spec digest را در Report کنار هم ثبت کنید؛
- Breaking change را با Consumer واقعی یا Contract Test تأیید کنید.
تفاوت Documentation با Runtime میتواند خطای Spec، Implementation یا Deploy باشد. Harness نباید بدون Evidence یکی را حقیقت مطلق فرض کند.
HTTP semantics را واقعاً تست کنید
RFC 9110 Semantics مشترک HTTP را مستقل از HTTP/۱.۱، HTTP/۲ و HTTP/۳ تعریف میکند. تست API فقط Status code نیست؛ Method، Representation، Header، Condition، Content negotiation، Redirect و Cache behavior بخشی از قراردادند.
Method و اثر
| موضوع | آزمون | Oracle |
|---|---|---|
| Safe method | GET/HEAD تکراری | اثر کسبوکار ناخواسته ندارد |
| Idempotent intent | PUT/DELETE تکراری | State نهایی سازگار؛ اثر اضافی ندارد |
| Conditional update | If-Match stale/current | lost update رد میشود |
| Content negotiation | Accept supported/unsupported | Representation یا reject قراردادی |
| Redirect | 3xx با Method/Body حساس | Client رفتار مجاز و مقصد امن |
| HEAD | مقایسه metadata با GET | Body ندارد؛ Header معنا دارد |
«PUT همیشه Idempotent است» Assertion عملی نیست؛ Semantics مورد انتظار را با State و Side effect خود سیستم بسنجید. همچنین Retry Client ممکن است Method را خودکار تکرار کند یا Redirect را دنبال کند؛ Raw attempts را ثبت کنید.
Error contract با RFC ۹۴۵۷
RFC 9457 Problem Details قالب ماشینخوان خطای HTTP را با type، status، title، detail و instance تعریف و RFC ۷۸۰۷ را منسوخ کرده است. type شناسه اصلی نوع مسئله است؛ متن فارسی/انگلیسی detail شناسه پایدار برای Automation نیست.
Oracle پاسخ خطا
- Status با semantics عمومی و Problem type با Domain سازگار باشد؛
Content-Type: application/problem+jsonطبق قرارداد باشد؛typeپایدار و مستند؛instanceدر صورت استفاده قابلردیابی باشد؛- Extension memberها type و version روشن داشته باشند؛
- Stack trace، SQL، Secret، Token، PAN یا PII در Detail نیاید؛
- خطای Validation ترتیب/Path/Code ماشینخوان و قابلپیشبینی داشته باشد؛
- Error نباید اثر ممنوع ایجاد کرده باشد.
assert(response.status === 409)
assert(response.headers.contentType === "application/problem+json")
assert(response.body.type === "https://api.example/problems/idempotency-conflict")
assert(response.body.status === 409)
assert(!containsSecret(response.body))
assert(await probes.countDebits(orderKey) === 1)
State transition را مستقل از Response ببینید
API یک ماشین حالت پنهان دارد. فقط Body آخر را نبینید؛ Pre-state، Command، Post-state، Event و Transition ممنوع را مشخص کنید.
Allowed:
Draft -> PendingPayment -> Paid -> Fulfilled
\-> Failed
Paid -> RefundPending -> Refunded
Forbidden examples:
Fulfilled -> Draft
Refunded -> Paid
Failed -> Fulfilled without approved recovery
State probe چگونه Implementation coupling را کم کند؟
- ترجیح اول: API یا read model مستقل و مجاز؛
- بعد: Domain event/audit record با Correlation؛
- بعد: Test-only probe محدود، authenticated و غیرProduction؛
- آخر: Query مستقیم datastore برای تست Component با Schema-aware helper.
Query مستقیم هر Table ممکن است تست را به Implementation قفل کند. Probe را بر مفهوم Domain مانند «یک Debit مؤثر» بسازید و Adapter زیرین را جدا نگه دارید. راهنمای طراحی برای تستپذیری قرارداد Control/Observe/Oracle را برای چنین Seamهایی عمیق میکند.
Side-effect oracle: وجود و عدموجود را هر دو بسنجید
Assertion مثبت میگوید Order ساخته شد؛ Assertion منفی میگوید Charge دوم، Event دوم یا Notification قبل از Commit ساخته نشد. در APIهای Command، نبودن اثر ممنوع اغلب مهمتر از Body است.
| Command | اثر لازم | اثر ممنوع |
|---|---|---|
| Create order | Order + outbox event | Debit پیش از Payment |
| Confirm payment | یک Ledger debit + Paid | دو debit/دو event |
| Reject refund | reason/audit | تغییر balance |
| Invalid request | Problem response/audit حداقلی | write/domain event |
| Unauthorized read | deny + safe audit | data disclosure/cache fill |
Idempotency را با Sequence و Ambiguity تست کنید
فقط دو بار فرستادن Request پشت سر هم کافی نیست. خطر واقعی زمانی است که Client نمیداند Attempt اول Commit شده یا نه.
ماتریس Idempotency key
| Sequence | انتظار Response | انتظار State |
|---|---|---|
| same key + same bytes | همان نتیجه/Reference قراردادی | یک اثر |
| same key + semantic same | Policy صریح | یک اثر |
| same key + changed amount | Conflict | بدون اثر تازه |
| same key + other actor/tenant | deny/independent per policy | بدون leakage/collision |
| retry while first in-flight | wait/conflict/replay policy | یک winner |
| retry after timeout-after-commit | نتیجه قبلی قابلبازیابی | یک effect |
| retry after key expiry | رفتار مستند | ریسک صریح |
Canonicalization را مشخص کنید: آیا تفاوت ترتیب JSON key، فاصله یا شکل اعداد «همان Request» است؟ Harness باید Raw bytes و normalized business payload را هر دو نگه دارد و با Contract Server مقایسه کند.
Retry، Timeout و Attempt را قاطی نکنید
یک فراخوانی منطقی ممکن است چند Attempt فیزیکی داشته باشد. Retry خودکار Client میتواند Bug را پنهان کند یا بار را چند برابر سازد.
- Connect، TLS، response-header و response-body timeout را تفکیک کنید؛
- Attempt count، delay/backoff/jitter و Reason را ثبت کنید؛
- Retryable/non-retryable را بر Method، error و commit ambiguity تعریف کنید؛
- Deadline کلی را از timeout هر Attempt جدا کنید؛
- Client، Proxy و Service retry را همزمان روشن نگذارید مگر بودجه محاسبه شده باشد؛
Retry-Afterرا برای پاسخهای قراردادی آزمون کنید.
OpenTelemetry HTTP semantic conventions Attemptهای resend را با http.request.resend_count مدل میکند و میان خطای Network و Status دریافتی تفاوت میگذارد. این Trace جای Oracle کسبوکار نیست، اما علت تعداد Attemptها را روشن میکند.
Concurrency را با Barrier قطعی بسازید
دو Promise همزمان لزوماً به Race موردنظر نمیرسند. Harness باید هر دو Actor را تا یک Barrier ببرد، سپس Release کند و State نهایی را بسنجد.
prepare inventory=1 and version=7
actorA.ready(); actorB.ready()
barrier.releaseBoth()
[a, b] = await Promise.all([
reserve({sku, expectedVersion: 7, actor: "A"}),
reserve({sku, expectedVersion: 7, actor: "B"})
])
assert(exactlyOneSucceeded(a, b))
assert(await inventory(sku) === 0)
assert(await reservationCount(sku) === 1)
assert(await emittedEventCount(sku) === 1)
Raceهای مهم API
- آخرین موجودی یا ظرفیت؛
- Update با ETag/If-Match یکسان؛
- دو مصرف همزمان یک Coupon؛
- Refresh/revoke یا approve/cancel همزمان؛
- دو Idempotency request در حالت in-flight؛
- Callback و Poll همزمان برای Payment؛
- Page traversal در حالی که Dataset تغییر میکند.
Oracle Race فقط «یکی ۲۰۰ و دیگری ۴۰۹» نیست. State، count، constraint، event و balance نهایی را هم بسنجید و Test را چند Trial با Seed/Timing ثبتشده تکرار کنید.
Pagination، Filter و Sort یک قرارداد مجموعهاند
هر Page میتواند Schema-valid باشد، ولی مجموعه نهایی غلط باشد. یک Dataset کوچک با شناسه و Sort key کنترلشده بسازید و خواص زیر را بسنجید:
- اجتماع Pageها دقیقاً با مجموعه موردانتظار برابر است؛
- میان Pageها Duplicate یا Gap نیست؛
- Sort پایدار و tie-breaker صریح است؛
- Cursor با Filter/Sort/Tenant دیگر قابلاستفاده نیست؛
- Cursor tamper رد میشود و Secret داخلی افشا نمیکند؛
- Empty/last page و page-size boundary درستاند؛
- Insert/Delete همزمان semantics مستند Snapshot/Best-effort را نقض نمیکند؛
- Total count در صورت ارائه با همان Snapshot/Filter معنا دارد.
expected = sort(filter(seedDataset, query), contractOrder)
observed = []
cursor = null
do {
page = await list({ ...query, cursor })
assert(noDuplicateWithin(page.items))
observed.push(...page.items)
cursor = page.nextCursor
} while (cursor)
assert(deepEqual(ids(observed), ids(expected)))
Cache را با Actor، Header و Invalidation بسنجید
RFC 9111 HTTP caching و Cache key/reuse را تعریف میکند. تست فقط Cache-Control نیست؛ باید ثابت کند Representation نامناسب ذخیره یا برای Request دیگری reuse نشده است.
سناریوهای Cache
- Cold→warm و Age/validator behavior؛
- ETag + If-None-Match و Last-Modified در مرز؛
Varyبرای Accept/Encoding/Language و Header قراردادی؛- Authorization/Cookie/Tenant و جلوگیری از cross-user reuse؛
- Mutation سپس read و invalidation/freshness؛
- Error، ۴۰۴ و negative caching طبق Policy؛
- Query order/normalization و Cache-key collision؛
- CDN/Proxy/Service cache بهتفکیک، نه فقط Local client cache.
برای آزمون Cache، Client cache را آگاهانه روشن/خاموش کنید. اضافهکردن query تصادفی برای «دورزدن Cache» خودِ رفتار مورد آزمون را حذف میکند.
Authorization را با Actor×Action×Object×Tenant بسنجید
داشتن Token معتبر فقط Authentication را نشان میدهد. Matrix حداقل شامل Owner، other user همان Tenant، User Tenant دیگر، Role privileged محدود و Anonymous باشد.
| Actor | Read own | Read other same tenant | Read other tenant | Update |
|---|---|---|---|---|
| Customer A | allow | deny | deny | own only |
| Support tenant 17 | scoped allow | scoped allow | deny | policy-specific |
| Customer B / tenant 18 | own only | n/a | deny | own only |
OWASP API Security Top 10 2023 یک سند Awareness است؛ Test plan کامل یا Risk assessment اختصاصی شما نیست. BOLA/BFLA، sensitive flow، inventory و unsafe API consumption را به Threat/Asset محلی و Oracle نبودن افشا/اثر وصل کنید. برای OAuth/OIDC نیز راهنمای تست امنیت OAuth ۲.۰ و OIDC مرز Token و Resource Server را عمیق میکند.
Rate limit و Backoff را بدون ادعای استاندارد نادرست تست کنید
RFC 6585 کد 429 Too Many Requests را تعریف میکند و اجازه میدهد پاسخ Retry-After داشته باشد؛ الگوریتم شمارش User/Resource/Server را تعیین نمیکند. بنابراین Policy واقعی API—نه حدس Harness—مبنای Test است.
- Partition key: Actor/API key/IP/Tenant/Operation؛
- Window، Burst و Cost هر Operation؛
- آیا درخواست نامعتبر/ناموفق سهمیه مصرف میکند؟
- Boundary قبل/بعد reset با Clock کنترلشده؛
- Concurrent requests و atomicity شمارنده؛
- ۴۲۹ schema، Retry-After و عدم Cache نامناسب؛
- Client backoff/jitter و نبود retry storm؛
- نبود leakage از quota یک Tenant به Tenant دیگر.
پیشنویس RateLimit fields هنوز Internet-Draft است، نه RFC نهایی؛ اگر API آن را پیاده میکند، Version و Contract داخلی را تاریخدار کنید و از زبان «استاندارد نهایی» استفاده نکنید.
Dependency واقعی، Fake، Simulator یا Sandbox؟
| نوع | مزیت | چیزی که اثبات نمیکند |
|---|---|---|
| Stub/Mock | خطا/ترتیب قطعی و سریع | Protocol/Provider drift واقعی |
| Fake | رفتار سبک و Stateدار | Semantics Engine واقعی |
| Simulator | State/failure بیرونی کنترلشده | همه Quirkهای Provider |
| Real container | Protocol/engine fidelity | Production topology/scale |
| Provider sandbox | قرارداد اجرایی شریک | Production parity کامل |
| Staging/E2E | Config/routing چندجزئی | Isolation/diagnosis آسان |
در Component lane، DB/Cache/Broker واقعی موقت معمولاً ارزشمند است؛ راهنماهای رسمی Testcontainers نمونههایی برای Database، Kafka و REST integration ارائه میکنند. این تکنیک همه Dependencyها را واقعی نمیکند و Production equivalence هم نمیسازد.
برای سرویس بیرونی، Virtualization باید رفتار Success، business rejection، timeout، malformed payload، duplicate callback و protocol error را بسازد. سپس Contract drift را با Provider واقعی/Sandbox محدود و مجاز بسنجید. راهنمای تست یکپارچهسازی مرز Real/Double/Sandbox و Failure matrix را تکمیل میکند.
Test data باید State و تاریخچه بسازد
یک CSV از ورودیها برای API stateful کافی نیست. Data builder باید Graph و تاریخچه بسازد: Customer→Cart→Order→Payment attempts→Ledger→Events.
Data contract ایرانی
- Canonical money با واحد صریح IRR و نمایش تومان جدا؛
- اعداد فارسی/عربی/لاتین و separatorهای مختلف فقط در Boundaryهای مجاز؛
- ی/ی، ک/ک، ZWNJ و normalization بدون merge هویتها؛
- شماره موبایل +۹۸/۰ بهعنوان Attribute، نه شناسه جهانی؛
- UTC برای Instant و Asia/Tehran برای نمایش/قانون محلی؛
- شناسه PSP و Merchant/Terminal در Namespace تست؛
- کارت/کدملی/Token کاملاً مصنوعی و غیرواقعی؛
- Run ID، deterministic seed و TTL پاکسازی.
برای Factory، Schema-driven، Property-based و داده ترکیبی به راهنمای تولید داده تست مراجعه کنید. Generator باید داده معتبر و نامعتبر را طبق Domain بسازد؛ Random بدون Seed و Oracle فقط شکست غیرقابلبازتولید تولید میکند.
Evidence bundle: شکست باید قابلتشخیص باشد
Report خوب فقط Request/Response خام نیست. Bundle حداقل این موارد را پیوند دهد:
- Test/Run/Attempt ID و timestamp UTC؛
- Artifact/Spec/Config/Harness/Data/Dependency version؛
- Actor/Role/Tenant/Objectهای synthetic؛
- Request method/URL template/headerهای مجاز/body digest؛
- Response status/header/body redacted؛
- Pre/post state و Side-effect counts؛
- Trace ID/Spanهای کلیدی و تعداد resend؛
- Oracleهای Pass/Fail با Expected/Actual؛
- Cleanup status و residual data؛
- Verdict: passed/failed/inconclusive/invalid.
W3C Trace Context قالب traceparent/tracestate را برای propagation میان اجزا استاندارد میکند. Harness میتواند Run ID را جدا نگه دارد و Trace ID را بهعنوان Evidence دریافت کند؛ Test نباید یک Trace ID دلخواه را معیار کسبوکار یا راز احراز هویت فرض کند.
Evidence را امن نگه دارید
- Authorization، Cookie، API key، Refresh token و Secret را Capture نکنید؛
- PII را با Allowlist و Redaction ساختاری حذف کنید؛
- Body بزرگ/Binary را با digest و نمونه محدود نگه دارید؛
- Retention، access و deletion evidence را تعریف کنید؛
- Raw evidence حساس را در Artifact عمومی CI منتشر نکنید؛
- خطای Redaction باید Run را invalid کند، نه اینکه Secret را عبور دهد.
Scan health معادل API test run health
قبل از Verdict رفتار API، اعتبار خود Run را بسنجید:
| وضعیت Run | معنا | Gate |
|---|---|---|
| Valid | Setup/identity/dependency/probes/cleanup سالم | Verdict قابلاستفاده |
| Degraded | شاهد غیربحرانی ناقص ولی Oracle اصلی معتبر | Policy و هشدار |
| Inconclusive | نتیجه رفتار قابلتعیین نیست | نه Pass نه Fail محصول |
| Invalid | Spec/version/setup/auth/probe غلط یا unsafe | خود Run باید Fail شود |
مثلاً ۴۰۱ ناشی از Token منقضی در Test «ایجاد سفارش موفق» Fail محصول نیست؛ Run inconclusive/invalid است. اما در Test «رد Token منقضی»، همان ۴۰۱ بخشی از Verdict درست است. Context Test تعیین میکند.
CI/CD را بر Risk و بودجه بازخورد لایهبندی کنید
| Lane | محتوا | بودجه |
|---|---|---|
| Pre-commit | unit oracle/schema/example lint | ثانیه |
| PR | contract + component API + critical negative | دقیقه |
| Post-merge | real DB/cache/broker + failure tests | دهها دقیقه |
| Nightly | race/property/collection/cache/full matrix | ساعت |
| Pre-release | sandbox/E2E/compatibility/security pack | Risk-based |
| Post-deploy | safe synthetic/read-only smoke | پیوسته/رویداد |
Quality Gate نمونه
- تمام Runهای اجباری Valid باشند؛
- هیچ Critical business/security Oracle شکست نخورده باشد؛
- Contract drift حل یا expiring exception داشته باشد؛
- Idempotency/timeout/race regressionهای مسیر مالی پاس باشند؛
- False-pass mutation suite افت نکرده باشد؛
- Cleanup و Redaction کامل باشند؛
- Flaky/Quarantine بدون Owner و expiry وجود نداشته باشد.
Retry تست شکستخورده را سبز نکند. First-attempt result، retry result و final policy را جدا گزارش کنید. Test flaky یک Signal نگهداری است؛ «دو بار دیگر اجرا کن» علت را حذف نمیکند.
Mutation برای سنجش قدرت Oracle
Coverage endpoint یا تعداد Assertion نمیگوید Harness خطای مهم را میگیرد. Mutationهای Domain/Infrastructure کنترلشده بسازید:
- Response صحیح ولی Ledger amount اشتباه؛
- Status error همراه با Side effect؛
- Idempotency key نادیده گرفته شود؛
- Tenant از Cache key حذف شود؛
- یک Item در مرز Page Duplicate شود؛
- If-Match نادیده گرفته شود؛
- Retry دو Attempt را به دو Trace بیربط تبدیل کند؛
- Problem detail یک Secret تزریقشده را بازتاب دهد؛
- Dependency timeout بعد از Commit ایجاد شود؛
- Cleanup یک Record را باقی بگذارد.
Mutation باید در محیط مجاز و مصنوعی اجرا شود و Mechanism موردنظر را واقعاً فعال کند. اگر Harness Mutation بحرانی را Kill نمیکند، Test سبز آن Evidence کافی نیست.
آزمایش قطعی Oracle چندلایه
یک برنامه مستقل با Node.js ۲۴.۱۸.۰ روی ده Case کاملاً ساختگی اجرا شد. Oracle ساده فقط Status مورد انتظار و Schema را سنجید؛ Oracle عمیق Domain، Side effect، Security، Collection و Evidence را نیز لازم دانست.
cases=10 naive_pass=10 deep_pass=3 false_passes_prevented=7
false_pass_ids=duplicate_retry,conflict_with_hidden_charge,timeout_after_commit,tenant_cache_leak,pagination_duplicate,stale_patch_overwrite,problem_detail_leaks_secret
decision=adopt_multilayer_oracle_and_state_probes
تفسیر نتیجه
بررسی ساده هر ده تعامل را Pass اعلام کرد؛ Oracle چندلایه فقط سه مورد را سالم دانست و هفت False Pass را گرفت. یافتهها شامل اثر تکراری Retry، برداشت پنهان در Conflict، Commit مبهم پس از Timeout، نشت Tenant در Cache، Duplicate Pagination، Lost update و افشای Secret در Error بود.
این نتیجه Benchmark ابزار، API یا Framework نیست. Dataset ساختگی/کوچک است، Performance و real network را مدل نمیکند و نشان نمیدهد هر Test همیشه به همه Probeها نیاز دارد. هدف، Demonstration معماری تصمیم است: Status+Schema شرط لازماند، نه Verdict کامل.
سناریوی کامل: API پرداخت فروشگاه ایرانی
جریان ساختگی شامل Checkout API، Order DB، Ledger، Outbox و PSP simulator است. مبلغ Canonical برابر 1,250,000 IRR و UI آن را 125,000 تومان نشان میدهد. هیچ کارت، کدملی، PSP یا داده Production واقعی استفاده نمیشود.
Golden flow
- Customer tenant-۱۷ با Cart نسخه ۴ درخواست Order میدهد؛
- Request یک Idempotency key و amount/currency صریح دارد؛
- Order در
PendingPaymentو Payment attempt ساخته میشود؛ - PSP simulator Authority مصنوعی برمیگرداند؛
- Callback signed simulator دریافت و مبلغ/merchant/order تطبیق میشود؛
- یک Ledger debit و یک outbox event ثبت میشود؛
- GET Order فقط برای Customer/Support مجاز همان Tenant قابلدیدن است؛
- Replay Callback پاسخ قراردادی میدهد ولی اثر تازه ندارد.
ماتریس Risk→Stimulus→Oracle
| ریسک | Stimulus | Oracle مستقل |
|---|---|---|
| ریال/تومان | amount 125000/1250000 | canonical ledger + explicit currency |
| Duplicate order | same-key retry | one order/debit/event |
| Ambiguous commit | drop response after write | retry retrieves same result |
| Callback replay | same Authority twice | one state transition |
| Cross-tenant | tenant-18 queries ID | deny + no cache/data leak |
| Lost update | two If-Match version 4 | one accepted; version monotonic |
| Pagination drift | orders sorted same timestamp | stable ID tie-breaker |
| Error leak | malformed PSP detail | safe Problem Details |
| Outbox failure | broker unavailable | durable pending event/recovery |
| Tehran boundary | local midnight/retry | UTC instant + policy date |
شرایط ایران و سناریوی unavailable
API Test SaaS، Cloud agent، Package registry، image registry، license server یا Sandbox خارجی ممکن است در شبکه، پرداخت، Account، Region یا زمان مشخص در دسترس نباشد. این وضعیت باید با Evidence تاریخدار سنجیده شود؛ نه با ادعای کلی درباره همه Vendorها یا همه زمانها.
- آیا Runner/CLI و Dependency image آفلاین یا Mirror میشوند؟
- آیا License expiry اجرای CI را متوقف میکند؟
- Source، API payload یا Evidence از کشور خارج میشود؟
- آیا Secret در Workspace/Cloud report ذخیره میشود؟
- اگر PSP Sandbox یا SaaS unavailable شد، Simulator معتبر چیست؟
- Artifact/Result/Raw evidence قابل Export و Replay هستند؟
- Clock/Timezone/Locale Agent خارجی با قرارداد ایران سازگار است؟
- Fallback چه Coverage gap و residual riskی باقی میگذارد؟
PoC دو هفتهای برای انتخاب Stack Harness
روز ۱ تا ۲: قرارداد تصمیم
پنج ریسک اصلی، Artifact/Protocol، Skill، بودجه CI، Security/data و Hard gateها را Freeze کنید. «Tool محبوب» مسئله قابلاندازهگیری نیست.
روز ۳ تا ۵: Vertical slice
Create→Read→Invalid→Cleanup را با Spec، Factory، Identity، State/side-effect probe، Report و Trace اجرا کنید.
روز ۶ تا ۸: Failure و Mutation
Timeout-after-commit، duplicate retry، stale update، cross-tenant، pagination duplicate و Error leak را تزریق کنید؛ False Passها را بشمارید.
روز ۹ تا ۱۰: CI و Parallelism
CLI headless، Exit code، shard، namespace، secret injection، JUnit/JSON artifact، flaky classification و cleanup را در Runner واقعی بیازمایید.
روز ۱۱ تا ۱۲: Change exercise
Schema/endpoint/error/domain change معقول اعمال کنید؛ Diff، review، failure diagnosis و repair effort را اندازه بگیرید.
روز ۱۳ تا ۱۴: Decision
Oracle strength، fidelity، speed، diagnosis، maintainability، CI، security، Iran access، TCO و Exit را با Evidence امتیاز دهید. ممکن است Stack ترکیبی GUI برای Exploration و Code-first برای Gate بهترین باشد.
معیارهای سالم Harness
- درصد ریسکهای Critical با Oracle چندلایه معتبر؛
- Mutation kill rate به تفکیک Failure mechanism؛
- False Pass/False Fail تأییدشده؛
- First-attempt pass rate و Flake rate؛
- Setup/run/cleanup/diagnosis duration؛
- Inconclusive/invalid rate و علت؛
- Contract/runtime drift پیش از Release؛
- Duplicate/side-effect defects کشفشده پیش از Production؛
- Evidence completeness و redaction violations؛
- Escaped API defect بر Failure mechanism، نه صرف count.
Endpoint coverage، Assertion count، Collection size و تعداد 200ها KPI تصمیم نیستند. Metric باید به Risk و قدرت کشف خطا وصل شود.
۱۵ ضدالگوی تست API پیشرفته
- Pass بر اساس Status code؛
- Schema برابر صحت Domain؛
- Snapshot کامل Body با ID/time تصادفی؛
- یک Token admin برای همه Testها؛
- Retry پنهان Client؛
- Sleep ثابت برای State async؛
- دو Promise بهعنوان Race قطعی؛
- هر Page معتبر پس Collection معتبر؛
- Query تصادفی برای دورزدن Cache؛
- Mock همه Dependencyها؛
- اعتماد کامل به Sandbox؛
- Random data بدون Seed/Manifest؛
- Log کامل Token/PII برای Debug؛
- ۴۰۱ setup failure بهعنوان Pass امنیتی؛
- Retry تا سبزشدن در CI.
چکلیست نهایی API Test Harness
- □ Risk، Requirement، Stimulus و Oracle جدا هستند.
- □ Artifact/Spec/Config/Dependency/Harness version ثبت میشود.
- □ Actor/Object/Tenant matrix وجود دارد.
- □ Data/Seed/Namespace/Clock کنترل میشوند.
- □ HTTP Method/Header/Condition/Cache semantics آزمودهاند.
- □ Success و Error schema/type/version دارند.
- □ Domain value و unit مستقل سنجیده میشوند.
- □ Pre/Post state و transition ممنوع دیده میشود.
- □ اثر لازم و نبود اثر ممنوع هر دو Oracle دارند.
- □ Idempotency با timeout و in-flight race آزموده شده است.
- □ Retry attempts و deadline آشکارند.
- □ Concurrency با Barrier و final-state oracle اجرا میشود.
- □ Pagination مجموعهای و Cache بین Actorها تست میشوند.
- □ Real/Fake/Simulator/Sandbox boundary مستند است.
- □ Trace/State/Response به Run ID پیوند دارند.
- □ Evidence Allowlist/redaction/retention دارد.
- □ Valid/Degraded/Inconclusive/Invalid تفکیک میشوند.
- □ Mutation suite قدرت False-pass detection را میسنجد.
- □ CI lane، Gate، Quarantine owner و expiry دارد.
- □ Iran access، offline fallback، Export و residual risk ثبتاند.
سؤالات متداول API Test Harness
آیا Status و Schema برای تست API کافیاند؟
خیر. آنها Interface را تا حدی میسنجند، اما Domain value، State، Side effect، Authorization، Collection، Cache، Idempotency و اثر Timeout را اثبات نمیکنند. Oracle را متناسب با ریسک گسترش دهید.
برای API Testing ابزار GUI بهتر است یا Code-first؟
پاسخ به Workflow بستگی دارد. GUI برای Exploration، collaboration و شروع سریع مفید است؛ Code-first معمولاً Composition، review، abstraction و CI عمیقتری میدهد. با یک Vertical slice و Mutation PoC تصمیم بگیرید، نه محبوبیت.
آیا باید در تست API مستقیماً Database را Query کنیم؟
برای Component test ممکن است یک State probe مفید باشد، ولی آن را پشت Domain-oriented adapter قرار دهید. برای System/E2E از Read model، audit یا API مستقل استفاده کنید. Query جدول خام میتواند Coupling و False confidence بسازد.
چگونه Idempotency API را تست کنیم؟
same-key/same-payload، key با payload متفاوت، concurrent in-flight، timeout-after-commit، actor/tenant دیگر و expiry را بسنجید. Response کافی نیست؛ تعداد Order، Ledger effect، Event و Notification باید دقیقاً مطابق Contract باشد.
Flaky API Test را Retry کنیم؟
Retry محدود برای جمعآوری Evidence ممکن است، اما First-attempt و retry را جدا گزارش کنید. تست را با علت، Owner و expiry قرنطینه کنید؛ Sleep و Retry نامحدود فقط Race، محیط یا Oracle ضعیف را پنهان میکنند.
جمعبندی
API Testing پیشرفته با Client گرانتر یا Assertion بیشتر آغاز نمیشود؛ با تعریف Risk و Truth قابلمشاهده آغاز میشود. Contract و Schema لازماند، اما Verdict معتبر باید State، Side effect، Security، Collection، Retry/Concurrency و Evidence را نیز بهاندازه ریسک پوشش دهد.
آزمایش ساختگی نشان داد status+schema هر ده Case را سبز کرد، در حالی که Oracle چندلایه هفت False Pass را آشکار ساخت. Harness را بهعنوان سیستم تصمیم طراحی کنید: Manifest را Freeze کنید، Attemptها را آشکار نگه دارید، اثر لازم و ممنوع را بسنجید، Failure را قطعی تزریق کنید، Evidence را امن جمع کنید و فقط Run معتبر را وارد Gate کنید.

