بسیاری از باگهای سخت با یک ورودی خاص پیدا نمیشوند؛ با «ترتیب» رویدادها ظاهر میشوند. پرداخت موفقی که پس از لغو سفارش میرسد، دکمه ارسال برای سفارش بازپرداختشده، یا Tokenی که پس از Logout هنوز معتبر است، همگی به تاریخچه سیستم وابستهاند.
تست انتقال حالت یا State Transition Testing این تاریخچه را به مدل تبدیل میکند: سیستم اکنون در چه حالتی است، چه رویدادی رخ میدهد، چه شرطی باید برقرار باشد، حالت بعدی چیست و چه اثری مجاز است. در این راهنما یک چرخه سفارش فروشگاه ایرانی را مدل میکنیم، جدول معتبر و نامعتبر میسازیم، معیارهای پوشش ISTQB را محاسبه میکنیم و از مدل Test Case استخراج میکنیم.
در پایان میتوانید:
- State، Event، Guard، Transition و Action را از هم تفکیک کنید؛
- نمودار و جدول انتقال حالت بسازید؛
- انتقال معتبر، نامعتبر، Self-transition و رویداد تکراری را تست کنید؛
- All States، Valid Transitions و All Transitions Coverage را درست به کار ببرید؛
- Race، Callback دیررس و State Explosion را مدیریت کنید.
تست انتقال حالت چیست؟
تست انتقال حالت یک تکنیک تست جعبهسیاه است که رفتار سیستم را بر اساس حالت فعلی و رویداد دریافتی بررسی میکند. «حالت» خلاصهای از تاریخچه مرتبط است؛ یعنی اطلاعات گذشتهای که روی واکنش بعدی سیستم اثر میگذارد. همان رویداد ممکن است در دو State نتیجه متفاوت داشته باشد.
برای مثال، رویداد cancel_order روی سفارش AwaitingPayment میتواند آن را مستقیماً Cancel کند، اما روی سفارش Paid شاید فرایند Refund بسازد و روی Delivered باید رد شود. این تفاوت دلیل استفاده از State Transition Testing است.
این تکنیک در خانواده تکنیکهای تست جعبه سیاه قرار میگیرد، چون انتظار را از رفتار قابل مشاهده و مدل مشخصات استخراج میکند؛ هرچند مشاهده کد میتواند در تکمیل مدل کمک کند.
چه چیزی واقعاً یک State است؟
هر صفحه، پیام یا مقدار موقت لزوماً State نیست. یک State باید دستکم برای تصمیم بعدی سیستم معنادار باشد.
آزمون تشخیص State
از خود بپرسید: «اگر همین Event را در این وضعیت و وضعیت دیگر ارسال کنم، آیا پاسخ یا اثر مجاز فرق میکند؟» اگر بله، احتمالاً تفاوت State مهمی دارید.
- Paid یک State معنادار است، چون Cancel و Ship روی آن قواعد خاص دارند.
- نمایش Toast سبز معمولاً خروجی UI است، نه State کسبوکار.
- سه تلاش ناموفق Login فقط Counter نیست؛ اگر رفتار بعدی را به Locked تغییر دهد، بخشی از State Model است.
- HTTP 409 State نیست؛ پاسخ به Transition نامعتبر است.
اشتباه رایج این است که «خطا» را بهعنوان State بسازیم، در حالی که سیستم پس از خطا در همان State قبلی مانده است. مثلاً تلاش برای ارسال سفارش Cancelled میتواند با ۴۰۹ رد شود و State همچنان Cancelled بماند.
اجزای مدل انتقال حالت
| جزء | تعریف | نمونه سفارش |
|---|---|---|
| State | وضعیت معنادار فعلی | Paid |
| Event | محرکی که بررسی Transition را آغاز میکند | payment_verified |
| Guard | شرط لازم برای مجاز بودن Transition | مبلغ و شناسه درگاه معتبر است |
| Transition | حرکت از State فعلی به بعدی | AwaitingPayment → Paid |
| Action | اثر همراه Transition | ثبت پرداخت و انتشار رویداد |
| Invariant | قاعدهای که در State باید همیشه برقرار باشد | سفارش Paid یک پرداخت Verified دارد |
قالب خواندن یک Transition
Current State -- Event [Guard] / Action --> Next State
برای نمونه:
AwaitingPayment
-- payment_verified [amount matches]
/ save payment, publish OrderPaid
--> Paid
اگر Guard برقرار نباشد، باید مشخص شود سیستم Event را رد میکند، در همان State میماند، Retry میکند یا به State جبرانی میرود. جای خالی در Requirement خودش یک Finding تحلیل است.
چه زمانی از State Transition Testing استفاده کنیم؟
- سفارش، پرداخت، بازپرداخت و ارسال؛
- Session، Login، MFA، Lock و Logout؛
- Ticket پشتیبانی و گردش تایید؛
- اشتراک: Trial، Active، Past Due، Suspended و Cancelled؛
- فایل: Draft، Review، Approved و Published؛
- پروتکل، اتصال، دستگاه IoT و سیستم تعبیهشده؛
- Workflowهای رویدادمحور و پردازش Async.
اگر خروجی فقط تابع یک ورودی مستقل است و تاریخچه اثری ندارد، تقسیمبندی همارزی و تحلیل مقدار مرزی معمولاً شروع بهتری است.
مثال عملی: چرخه سفارش و پرداخت
مدل نمونه را عمداً محدود میکنیم تا قابل فهم و تست باشد:
AwaitingPayment: سفارش ساخته شده و پرداخت تایید نشده است؛Paid: پرداخت سمت سرور Verify شده است؛Preparing: آمادهسازی شروع شده است؛Shipped: مرسوله تحویل حامل شده است؛Delivered: تحویل تایید شده است؛Cancelled: سفارش پیش از اثر مالی نهایی لغو شده است؛RefundPending: مبلغ دریافت شده و بازپرداخت در جریان است؛Refunded: بازپرداخت تایید شده است.
نمودار متنی مسیرهای اصلی
[Start] -- create_order --> [AwaitingPayment]
[AwaitingPayment] -- payment_verified --> [Paid]
[AwaitingPayment] -- payment_timeout --> [Cancelled]
[AwaitingPayment] -- cancel --> [Cancelled]
[Paid] -- start_preparing --> [Preparing]
[Paid] -- cancel [allowed] --> [RefundPending]
[Preparing] -- ship --> [Shipped]
[Shipped] -- delivery_confirmed --> [Delivered]
[RefundPending] -- refund_verified --> [Refunded]
این نمودار مسیرهای معتبر را سریع نشان میدهد، اما Transitionهای نامعتبر را پنهان میکند. برای پوشش دقیق باید جدول نیز بسازیم.
جدول انتقال حالت سفارش
| State فعلی | Event | Guard | State بعدی | Action/Output |
|---|---|---|---|---|
| AwaitingPayment | payment_verified | مرجع، مبلغ و سفارش معتبر | Paid | ثبت پرداخت؛ انتشار OrderPaid |
| AwaitingPayment | payment_timeout | هنوز پرداختی تایید نشده | Cancelled | آزادسازی رزرو کالا |
| AwaitingPayment | cancel | کاربر مالک سفارش است | Cancelled | آزادسازی رزرو |
| Paid | start_preparing | موجودی نهایی و عملیات مجاز | Preparing | ثبت شروع آمادهسازی |
| Paid | cancel | هنوز آمادهسازی شروع نشده | RefundPending | ساخت درخواست بازپرداخت |
| Preparing | ship | کد مرسوله معتبر | Shipped | ثبت کد؛ انتشار OrderShipped |
| Shipped | delivery_confirmed | شاهد تحویل معتبر | Delivered | ثبت زمان تحویل |
| RefundPending | refund_verified | مرجع بازپرداخت معتبر | Refunded | ثبت بازپرداخت |
در مدل واقعی باید Failure و Retry وابستگیها را نیز تعیین کنید. سؤال مهم این است که Failure فنی State کسبوکار را تغییر میدهد یا فقط عملیات را برای Retry نگه میدارد.
Transition معتبر، نامعتبر و Self-transition
Transition معتبر
در مدل تعریف شده و Guard آن برقرار است؛ مثل Paid → Preparing با Event شروع آمادهسازی.
Transition نامعتبر
ترکیب State و Event نباید انجام شود؛ مثل Delivered + cancel. انتظار فقط «خطا» نیست:
- State باید Delivered باقی بماند؛
- هیچ Refund یا آزادسازی موجودی ساخته نشود؛
- پاسخ قراردادی مشخص باشد؛
- رویداد نامعتبر در صورت نیاز Audit شود؛
- ارسال دوباره همان Event اثر جانبی نسازد.
Self-transition و رویداد بیاثر
در Self-transition سیستم از یک State به همان State بازمیگردد و ممکن است Action مجاز داشته باشد. این با Ignore کردن Event یکی نیست. برای مثال Callback تکراری پرداخت روی State Paid میتواند همان State را نگه دارد، اما باید مرجع را تشخیص دهد و اثر مالی/پیام تکراری نسازد. انتظار Idempotency را صریح بنویسید.
جدول Transitionهای نامعتبر
| State | Event نامعتبر | انتظار |
|---|---|---|
| Cancelled | ship | رد؛ State و موجودی بدون تغییر |
| Delivered | cancel | رد؛ هدایت به Flow مرجوعی در صورت وجود |
| AwaitingPayment | delivery_confirmed | رد؛ هیچ Timestamp تحویل ثبت نشود |
| Refunded | start_preparing | رد و Alert/Audit متناسب |
| Preparing | payment_timeout | Event قدیمی نادیده/ثبت شود؛ State عقب نرود |
نوشتن جدول کامل State × Event شکاف Requirement را آشکار میکند. هر سلول باید یکی از اینها باشد: Transition معتبر، Self-transition، Ignore تعریفشده یا Transition نامعتبر با پاسخ مشخص.
ساخت مدل انتقال حالت؛ گام به گام
۱. Scope و حافظه مرتبط را تعیین کنید
آیا مدل فقط Payment Status است یا کل سفارش و ارسال؟ کدام اطلاعات گذشته واکنش آینده را عوض میکنند؟ Scope مبهم سریعاً به انفجار حالتها میرسد.
۲. Stateها را با نام پایدار تعریف کنید
برای هر State Entry Criteria، Exit Criteria و Invariant بنویسید. برچسب UI فارسی میتواند تغییر کند؛ Enum دامنه مانند REFUND_PENDING باید معنای قراردادی ثابت داشته باشد.
۳. Event و منبع آن را ثبت کنید
Event ممکن است از کاربر، Scheduler، درگاه پرداخت، انبار یا پیام Queue بیاید. Source، شناسه یکتا، Timestamp و امکان تکرار/تاخیر را مشخص کنید.
۴. Guard و Action را جدا کنید
Guard تصمیم میگیرد Transition مجاز است؛ Action اثر آن است. «اگر موجودی هست، رزرو کن» دو مفهوم را مخلوط میکند. بهتر است شرط موجودی و اثر رزرو جدا قابل آزمون باشند.
۵. نمودار و جدول را تطبیق دهید
نمودار برای فهم، جدول برای Completeness. تعداد State و Transition باید بین دو مدل سازگار باشد. Transition موجود در یکی و غایب در دیگری نشانه ابهام است.
۶. سلولهای نامشخص را با تیم حل کنید
تستر نباید رفتار نامعلوم را حدس بزند. Product، توسعه، عملیات و امنیت باید مشخص کنند Late Event، Duplicate، Timeout و Unauthorized در هر State چه اثری دارد. این کار بخشی از تحلیل نیازمندیها است.
استخراج Test Case از مدل
| ID | Precondition | Event | State بعدی | اثرات قابل بررسی |
|---|---|---|---|---|
| ST-01 | AwaitingPayment؛ مرجع معتبر | payment_verified | Paid | یک Payment؛ یک Event؛ رزرو حفظ شود |
| ST-02 | AwaitingPayment؛ بدون پرداخت | payment_timeout | Cancelled | موجودی یک بار آزاد شود |
| ST-03 | Paid؛ آمادهسازی شروع نشده | cancel | RefundPending | یک Refund Request ساخته شود |
| ST-04 | Cancelled | ship | Cancelled | رد؛ هیچ Shipment ساخته نشود |
| ST-05 | Paid؛ Callback ثبتشده | همان payment_verified | Paid | بدون پرداخت/Event تکراری |
| ST-06 | Shipped | delivery_confirmed | Delivered | زمان و شاهد تحویل ثبت شود |
در هر Case، Precondition را با API/Fixture معتبر بسازید و بعد از اجرا هم State قابل مشاهده و هم اثرهای جانبی مانند DB، Event، موجودی و Audit Log را بررسی کنید. قالب حرفهای را در راهنمای نوشتن تستکیس ببینید.
معیارهای پوشش State Transition در ISTQB
All States Coverage
آیتمهای پوشش، Stateها هستند:
All States Coverage =
تعداد Stateهای بازدیدشده / کل Stateهای شناساییشده × 100
این معیار ضعیفتر است؛ میتوان همه Stateها را دید ولی بعضی مسیرهای ورود/خروج را هرگز اجرا نکرد.
Valid Transitions Coverage یا ۰-switch
آیتمهای پوشش، تکTransitionهای معتبر هستند:
Valid Transitions Coverage =
انتقالهای معتبر اجراشده / کل انتقالهای معتبر × 100
ISTQB CTFL v4.۰.۱ این معیار را پرکاربردترین معیار معرفی میکند. رسیدن به ۱۰۰٪ Valid Transitions معمولاً All States را نیز پوشش میدهد، اما Transition نامعتبر را تضمین نمیکند.
All Transitions Coverage
هم Transitionهای معتبر و هم تلاش برای Transitionهای نامعتبرِ جدول را شامل میشود:
All Transitions Coverage =
انتقالهای معتبر اجراشده + نامعتبرهای تلاششده
------------------------------------------------ × 100
کل انتقالهای معتبر و نامعتبر شناساییشده
هر Transition نامعتبر را در یک Test Case جدا امتحان کنید تا یک Defect مانع مشاهده Defect بعدی نشود. طبق CTFL v4.۰.۱، All Transitions برای نرمافزار Mission/Safety-critical باید حداقل در نظر گرفته شود؛ برای محصول معمولی سطح پوشش را مبتنی بر ریسک انتخاب و ثبت کنید.
N-switch Coverage و توالیهای طولانیتر
۰-switch یک Transition را پوشش میدهد. N-switch توالیهای N+1 Transition را میسنجد؛ بنابراین ۱-switch زوجهای دو Transition متوالی را هدف میگیرد. مثال:
AwaitingPayment → Paid → Preparing
چرا مهم است؟ ممکن است هر Transition جداگانه Pass شود، اما اثر Transition اول پیششرط ناقصی برای دومی بسازد. پوشش با افزایش N بهسرعت رشد میکند و Loopها All Paths را عملاً نامتناهی میکنند؛ توالیهای حساس را بر اساس ریسک انتخاب کنید.
تست رویداد تکراری، دیررس و خارج از ترتیب
در سامانه Async، Eventها ممکن است Duplicate، Delayed یا Reordered باشند. برای پرداخت این موارد را مدل کنید:
- Callback موفق دو بار با یک مرجع؛
- Timeout پیش از Callback موفق؛
- Callback قدیمی پس از ایجاد پرداخت جدید؛
- Refund تاییدشده پیش از رسیدن پاسخ Polling؛
- دو Worker که همزمان Transition را میخواهند؛
- Retry پس از Timeout شبکه، در حالی که عملیات سرور انجام شده است.
مثال Race مهم
سفارش AwaitingPayment است. Scheduler آن را Cancel میکند و تقریباً همزمان Callback پرداخت میرسد. انتظار باید روشن باشد: آیا سیستم با کنترل Version فقط یکی را میپذیرد؟ آیا پرداخت دیررس به RefundPending میرود؟ آیا Alert ساخته میشود؟ State Transition Model ساده بهتنهایی همزمانی را حل نمیکند، اما نقطه تصمیم را برای تست Race آشکار میسازد.
State را در API چگونه Assert کنیم؟
فقط Response همان فرمان کافی نیست. برای Transition حیاتی، چند سطح شاهد بگیرید:
- Response Code و Body قراردادی؛
- خواندن مجدد Resource و بررسی State/Version؛
- اثر پایدار مانند Payment/Shipment/Inventory؛
- Event منتشرشده با شناسه همبستگی؛
- نبود اثر تکراری یا نامجاز؛
- Audit Log بدون داده حساس؛
- رفتار Retry همان Event.
برای Status، Contract، Authorization و Idempotency به راهنمای تست API مراجعه کنید.
ترکیب State Transition با تکنیکهای دیگر
| تکنیک | نقش مکمل |
|---|---|
| EP/BVA | مبلغ، Quantity، Timeout و مرز Guard در هر Transition |
| Decision Table | ترکیب Role، State، موجودی و شرایط لغو |
| Use Case | مسیر End-to-End بازیگران و Alternative Flow |
| White-box | بررسی Branch، Lock و Error Path پیادهسازی |
| Exploratory | کشف Event و توالی پیشبینینشده اطراف مدل |
اگر Guard چند شرط دارد، تست جدول تصمیم از ایجاد دهها State ترکیبی جلوگیری میکند.
مدیریت State Explosion
اگر Payment، Fulfillment، Notification و Fraud را در یک State واحد ترکیب کنید، تعداد حالتها ضرب میشود. راههای کنترل:
- مدلها را بر اساس Bounded Context جدا کنید؛
- Payment Status و Fulfillment Status را دو محور مستقل نگه دارید؛
- Stateهای مشابه با رفتار یکسان را ادغام کنید؛
- Sub-state و مدل سلسلهمراتبی برای جزئیات بسازید؛
- Guard را جای State مصنوعی به کار ببرید؛
- توالیها را مبتنی بر ریسک انتخاب کنید؛
- Transition Matrix را Version Control و Review کنید.
کوچککردن مدل نباید رفتار مهم را حذف کند. هر سادهسازی و مورد خارج Scope را ثبت کنید.
اتوماسیون State Transition Testing
برای اتوماسیون، مدل را از Test Data جدا کنید. یک ردیف میتواند from_state، event، expected_state و expected_action داشته باشد. Runner برای هر ردیف:
- Fixture را به State مبدا میرساند؛
- Event را با شناسه یکتا ارسال میکند؛
- Response و State پایدار را Poll/Assert میکند؛
- اثر جانبی و عدم اثر اضافی را میسنجد؛
- داده را Cleanup میکند.
روی DB Production State را مستقیم تغییر ندهید. Factory/API آزمایشی قابل کنترل بسازید. تستهای سریع Valid Transition را در Pipeline و توالیهای Race/Async را در Lane جدا اجرا کنید. شکستها را با Build، State اولیه، Event ID و Timeline در فرایند اجرای تست ثبت کنید.
ملاحظات امنیتی مدل حالت
- Transition فقط در UI محدود نشود؛ API سمت سرور State و Role را بررسی کند؛
- کاربر نتواند مقدار State را مستقیماً به State دلخواه Patch کند؛
- Event قدیمی یا Replayشده اثر تکراری نسازد؛
- تغییر Role، Logout و Revocation Stateهای Session را بهروز کنند؛
- Transition حساس Audit شود، اما Token و داده حساس Log نشود؛
- خطای نامعتبر اطلاعاتی درباره Resource دیگر افشا نکند.
برای مدل تهدید، Authorization و ثبت امن Finding از راهنمای تست امنیت استفاده کنید.
ملاحظات محصولات ایرانی
- Redirect مرورگر از درگاه را معادل Payment Verified ندانید؛ Verify سمت سرور State را تغییر دهد؛
- Callback دیررس، تکراری و Timeout درگاه را در مدل داشته باشید؛
- پیامک «ارسال شد» State سفارش نیست؛ وضعیت Notification را جدا مدل کنید؛
- Timestamp داخلی را با قرارداد زمانی روشن، ترجیحاً UTC، ذخیره کنید و نمایش شمسی را لایه Presentation بدانید؛
- برچسب فارسی Stateها را از Enum پایدار Domain جدا نگه دارید؛
- اختلال Dependency نباید سفارش را بیدلیل به State موفق یا غیرقابل بازیابی ببرد.
اشتباهات رایج در تست انتقال حالت
- پیام خطا یا صفحه UI را بهجای State کسبوکار مدل میکنیم؛
- Counter و تاریخچهای که رفتار را عوض میکند از مدل حذف میشود؛
- فقط مسیر Happy Path نمودار میشود و جدول نامعتبر نداریم؛
- Guard، Action و State در یک عبارت مبهم مخلوط میشوند؛
- فقط Response را میبینیم و اثر جانبی را Assert نمیکنیم؛
- Duplicate، Retry، Late Event و Race را فراموش میکنیم؛
- ۱۰۰٪ State Coverage را معادل پوشش Transition میدانیم؛
- همه مسیرها را هدف میگیریم و با Loop به انفجار تست میرسیم؛
- مدل با Requirement و کد تغییر میکند اما Version آن بهروز نمیشود.
چکلیست State Transition Testing
- Scope و اطلاعات تاریخی موثر بر رفتار مشخص است.
- هر State تعریف، Entry/Exit و Invariant دارد.
- Event، منبع، شناسه و قابلیت Retry آن ثبت شده است.
- Guard از Action جدا شده است.
- State اولیه، نهایی و بازیابی مشخصاند.
- نمودار و جدول معتبر با هم سازگارند.
- هر ترکیب State × Event رفتار تعریفشده دارد.
- Transitionهای نامعتبر Test Case جدا دارند.
- Duplicate، Late، Reordered و Concurrent Event پوشش دارند.
- معیار پوشش انتخابشده و مخرج آن مستند است.
- State، اثر جانبی و نبود اثر اضافی Assert میشوند.
- Fixture تکرارپذیر و Cleanup امن داریم.
- مدل در Version Control و Review تیمی است.
- محدودیتها و Stateهای خارج Scope ثبت شدهاند.
سوالات متداول تست انتقال حالت
تفاوت State و Action چیست؟
State وضعیت معناداری است که روی رفتار بعدی اثر دارد؛ Action اثری است که هنگام Transition رخ میدهد. «Paid» State است، اما «ارسال رویداد OrderPaid» Action است.
۰-switch Coverage یعنی چه؟
در واژگان ISTQB CTFL v4.۰.۱ همان Valid Transitions Coverage است: هر Transition معتبر منفرد دستکم یک بار اجرا شود. این معیار Transitionهای نامعتبر یا زوج توالیها را تضمین نمیکند.
آیا باید همه مسیرهای ممکن را تست کنیم؟
معمولاً خیر. Loop و ترکیب Stateها تعداد مسیر را بسیار زیاد یا نامتناهی میکند. ابتدا Valid/All Transitions را بر اساس ریسک پوشش دهید و سپس توالیهای حساس N-switch، Retry و Race را انتخاب کنید.
نمودار بهتر است یا جدول انتقال حالت؟
نمودار برای فهم مسیرهای معتبر و ارتباط تیمی بهتر است؛ جدول برای دیدن همه ترکیبهای State/Event و Transition نامعتبر دقیقتر است. در کار حرفهای معمولاً هر دو مکمل هم هستند.
آیا State Transition فقط برای UI است؟
خیر. برای API، Workflow، Payment، Session، پروتکل و سیستم Async بسیار مفید است. معیار اصلی این است که رفتار به State فعلی یا تاریخچه رویدادها وابسته باشد.
جمعبندی
ارزش تست انتقال حالت در کشیدن چند دایره و پیکان نیست؛ در صریحکردن تصمیمهای پنهان است. State واقعی را از پیام و صفحه جدا کنید، Event و Guard و Action را دقیق بنویسید، جدول نامعتبر بسازید و State پایدار و اثرهای جانبی را با هم Assert کنید. سپس پوشش را با معیار درست ISTQB و توالیهای ریسکمحور اندازه بگیرید. این مدل وقتی بیشترین ارزش را دارد که Product، توسعه و تست قبل از Incident روی رفتار رویداد دیررس و نامعتبر توافق کنند.

