عبارت «Reschedule باید درست کار کند» برای انسانها آشناست اما برای تست، طراحی و ایجنت قابل اجرا نیست. «درست» یعنی چه وقتی بیمار در نیمهشب منطقهٔ زمانی دیگری است؟ اگر وقت جدید با رزرو موجود همپوشانی دارد چه میشود؟ اگر بیمار بعد از پنجرهٔ لغو تلاش کند؟ Example Mapping این صفت مبهم را به Rule، مثال و سؤال تبدیل میکند تا قبل از ساختن code، تیم بر رفتار قابل مشاهده توافق کند.
## اهداف یادگیری
پس از این درس میتوانی یک نیاز مبهم را با Example Mapping به Rule، Example و Question بشکنی، Rule را با EARS واضح کنی، مثال را به Given/When/Then تبدیل کنی، و برای timezone، پنجرهٔ لغو و double booking معیار پذیرش قابل آزمون بسازی.
## جای درس در چرخهٔ سرتاسری
این پایان Elicit و پلِ ورود به Specify است. درس ۶ ترتیب و پاسخ تصمیمهای پرریسک را تعیین کرد. اکنون خروجی، نقشهٔ مثال و Acceptance examples است: ورودی درسهای Behavioral Spec، Slice و TDD. هنوز نمیگوییم کدام table، queue یا library لازم است؛ رفتار مورد توافق را مینویسیم تا design و test به آن پاسخ دهند.
## Example Mapping: گفتوگوی فشرده، نه فرم کامل نیاز
Example Mapping یک روش همکاری community است که کارت نیاز را به **Ruleها**، **Exampleها** و **Questionها** میشکند. Rule جملهای دربارهٔ رفتار یا policy است. Example یک مورد مشخص با داده و نتیجهٔ قابل مشاهده است. Question مجهولی است که هنوز Rule یا مثال را مسدود میکند. بعضی تیمها Scope یا Assumption را نیز جدا ثبت میکنند؛ این کار مفید است اگر با Decision ledger درس ۵ سازگار بماند.
جلسه با یک کارت کوچک آغاز میشود: «به بیمار اجازه بده وقتش را جابهجا کند.» نخست Ruleها را بیرون میکشیم، نه UI و implementation را: «فقط مالک رزرو مجاز است»، «وقت جدید نباید با وقت همان پزشک همپوشانی داشته باشد»، «پس از پنجرهٔ لغو، نیاز به تأیید عملیات است»، «زمانها با timezone تعیینشدهٔ کلینیک مقایسه میشوند». سپس برای هر Rule نمونههای مثبت، منفی و مرزی میسازیم. Questionها مانع میشوند که خلأ policy زیر پوشش یک مثال خوشبینانه پنهان شود: «آیا بیمار میتواند به روز دیگری تغییر دهد؟»، «تغییر در DST چگونه نمایش مییابد؟»، «آیا درخواست تکراری idempotent است؟»
هدف، تولید تعداد زیاد مثال نیست. یک Example خوب دادهٔ لازم، رخداد، نتیجه و مشاهدهپذیری را دارد. «رزرو متعارض رد شود» هنوز Rule است. «رزرو ۱۰:۰۰ تا ۱۰:۳۰ پزشک ۷ در Tenant A وجود دارد؛ بیمار همان Tenant درخواست ۱۰:۱۵ تا ۱۰:۴۵ میدهد؛ پاسخ 409 و رزرو قدیمی تغییر نمیکند» مثال آزمونپذیر است. نمونه، علت انتخاب status code نیست؛ آن تصمیم فنی یا قرارداد API در Spec بعدی است، مگر ذینفع آن را بهعنوان رفتار بیرونی لازم بداند.
## Rule را با EARS روشن کن
EARS، سبک جملهبندی ساختیافته برای نیازهاست و مقالهٔ اصلی آن نشان میدهد چگونه محرک و پاسخ را آشکار کنیم. این قالب جادویی نیست و جای مثال را نمیگیرد، اما فعلهای مبهم را کم میکند.
- **Ubiquitous:** «سامانه باید شناسهٔ Tenant را در هر خواندن رزرو اعمال کند.»
- **Event-driven:** «وقتی بیمار درخواست reschedule میدهد، سامانه باید مجوز او را بررسی کند.»
- **State-driven:** «تا وقتی رزرو لغو نشده است، سامانه باید آن را در تعارضسنجی بگنجاند.»
- **Unwanted behavior:** «اگر بازهٔ جدید همپوشانی دارد، سامانه نباید رزرو موجود را تغییر دهد و باید تعارض را گزارش کند.»
- **Optional feature:** «وقتی پزشک قابلیت reschedule را فعال کرده است، سامانه باید گزینهٔ زمان جدید را نشان دهد.»
عبارت «سریع reschedule کند» با EARS کامل نمیشود، چون آستانه ندارد. باید بپرسیم زمان پاسخ کجا اندازهگیری میشود، چه صدکی، چه باری و چه اتفاقی در timeout میافتد. نیاز کیفیتی نیز Rule و مثال میخواهد. EARS فقط ساختار جمله میدهد؛ authority و معیار پذیرش از Elicitation میآیند.
## Given/When/Then: تأیید رفتار، نه صرفاً قالب تست
Dan North، Given/When/Then را راهی برای توصیف رفتار و گفتوگو معرفی کرد. **Given** پیششرط و دادهٔ مرتبط است؛ **When** یک رخداد واحد؛ **Then** نتیجهٔ قابل مشاهده. متن نباید جزئیات database یا selector UI را وارد کند، مگر آنها واقعاً قرارداد بیرونی باشند. یک سناریو باید دربارهٔ یک علت و یک انتظار اصلی باشد؛ چندین When معمولاً نشان میدهد Ruleها مخلوط شدهاند.
Scenario: ردکردن زمان همپوشان در همان Tenant
Given پزشک ۷ در Tenant A یک رزرو 10:00 تا 10:30 دارد
And پزشک ۷ در Tenant B یک رزرو مستقل دارد
When بیمار Tenant A زمان 10:15 تا 10:45 را درخواست میکند
Then سامانه تعارض را گزارش میکند
And رزروهای موجود تغییر نمیکننداین مثال مرز Tenant را هم نشان میدهد؛ اما برای اثبات اینکه Tenant B مانع A نیست، مثال جدا بنویسید. مثالهای نامتقارن کمک میکنند: ابتدای بازه برابر، انتهای بازه برابر، بازه کاملاً درون بازهٔ دیگر، و دو بازهٔ چسبیده. تصمیم «بازهٔ چسبیده مجاز است یا نه» policy است و قبل از test باید در ledger پاسخ داشته باشد.
## سه خانوادهٔ مثال برای booking-platform
۱. timezone و DST
Rule: «سامانه زمان رزرو را در timezone تأییدشدهٔ کلینیک تفسیر میکند و زمان محلی نامعتبر را بیصدا جابهجا نمیکند.»
Scenario: رد ساعت گمشده در شروع DST
Given timezone کلینیک America/New_York است
And تاریخ محلی 2026-03-08 است که ساعت 02:00 به 03:00 میپرد
And بیمار مالک رزرو است
When بیمار زمان 2026-03-08 02:15 را برای reschedule انتخاب میکند
Then سامانه زمان محلی نامعتبر را گزارش میکند
And زمان رزرو قبلی تغییر نمیکنداین سناریو فقط شاخهٔ DST را میسنجد. سؤال باز «آیا UI نزدیکترین زمان مجاز را پیشنهاد کند؟» همچنان policy است؛ تا تصمیم گرفته نشود، Then فقط رد و عدم تغییر را تأیید میکند.
۲. پنجرهٔ لغو
Rule: «تا پیش از deadline تأییدشده، مالک رزرو میتواند بدون approval عملیات لغو کند.»
Scenario: رد لغو دقیقاً در deadline
Given رزرو مالک در timezone کلینیک برای 2026-06-15 14:00 است
And policy میگوید لغو فقط پیش از 2026-06-14 14:00 مجاز است
And زمان فعلی 2026-06-14 14:00 است
When مالک درخواست لغو میدهد
Then سامانه نیاز به مسیر عملیات را گزارش میکند
And وضعیت رزرو تأییدشده باقی میمانداین مثال مرزی، معنای «پیش از» را تثبیت میکند و با شاخهٔ refund یا پیامک مخلوط نیست؛ آنها Non-goal هستند مگر Scope تغییر کند.
۳. double booking و همزمانی
Rule: «دو درخواست همزمان نمیتوانند برای یک پزشک و Tenant، بازهٔ همپوشانِ تأییدشده بسازند.»
Scenario: رد reschedule همپوشان
Given پزشک ۷ در Tenant A رزرو تأییدشدهٔ 2026-06-15 10:00 تا 10:30 دارد
And بیمار Tenant A مالک رزروی دیگر است
When بیمار زمان 2026-06-15 10:15 تا 10:45 را درخواست میکند
Then سامانه تعارض را گزارش میکند
And هر دو رزرو قبلی بدون تغییر میمانندبرای موفقیت، مثال جدا لازم است:
Scenario: انتقال موفق به بازهٔ آزاد
Given بیمار Tenant A مالک رزرو 2026-06-15 09:00 تا 09:30 است
And پزشک ۷ در همان Tenant در 2026-06-15 11:00 تا 11:30 رزروی ندارد
And درخواست پیش از deadline لغو میرسد
When بیمار زمان 2026-06-15 11:00 تا 11:30 را درخواست میکند
Then فقط همان رزرو به بازهٔ جدید منتقل میشوداین مثالها implementation قفل، transaction یا constraint را تعیین نمیکنند؛ طراحی فنی باید همان Oracle رفتاری را حفظ کند.
## مثال واقعی: نقشهٔ reschedule
**کارت:** بیمار زمان رزرو را تغییر میدهد. **Ruleها:** مالکیت بررسی شود؛ timezone کلینیک authority است؛ تعارض رد شود؛ پنجرهٔ لغو رعایت شود؛ Tenant جدا بماند. **Exampleها:** انتقال موفقِ ۲۰۲۶-۰۶-۱۵، تعارض ۱۰:۱۵ با رزرو ۱۰ تا ۱۰:۳۰، لغو دقیقاً در deadline، ساعت ۰۲:۱۵ در ۲۰۲۶-۰۳-۰۸ DST، و جداسازی Tenant. **Questionها:** آیا عملیات بعد از deadline تأیید میکند؟ بازههای چسبیده مجازند؟ درخواست تکراری چه پاسخ میدهد؟ **Non-goal:** لغو سریالی، refund و تغییر provider تقویم در این Slice نیست.
این نقشه، آمادهٔ Behavioral Spec است چون سؤالهای blocking پاسخ یا ledger دارند. اگر policy deadline نامعلوم باشد، به جای نوشتن Then خیالی، `DECIDE` کنید. Agent میتواند جدول Rule/Example/Question بسازد و testهای موجود را به Ruleها وصل کند، اما پاسخ مالی یا تجربهٔ بیمار را اختراع نمیکند.
مثال ضعیف و علت شکست
«کاربر باید بتواند رزرو را آسان و سریع جابهجا کند.» این جمله مالک، زمان، تعارض، timezone و نتیجهٔ خطا را پنهان میکند. ایجنت احتمالاً یک فرم میسازد و test آن فقط دکمه را کلیک میکند. مثال ضعیف دیگر، Given با ده وضعیت نامربوط و سه When است؛ شکست آن معلوم نمیکند کدام Rule نادرست بوده است.
مثال اصلاحشده
«Given بیمار Tenant A مالک رزرو ۲۰۲۶-۰۶-۱۵ ۰۹:۰۰ تا ۰۹:۳۰ است و بازهٔ ۱۱:۰۰ آزاد است، When پیش از deadline آن را درخواست میکند، Then فقط همان رزرو منتقل میشود.» برای همپوشانی و DST مثالهای جدا با Given مخصوص نوشته میشوند. این Ruleها به test، API و UX جهت میدهند اما implementation را قفل نمیکنند.
## آزمایشگاه: نقشهٔ مثال بساز
**ورودی:** «بیمار بتواند وقت را reschedule کند» و تصمیمهای timezone/لغو از ledger. **گامها:** ۱) کارت و سه Non-goal بنویس. ۲) دستکم چهار Rule استخراج کن. ۳) برای timezone، deadline و double booking یک مثال مثبت یا مرزی بساز. ۴) Questionهای policy را جدا کن. ۵) دو Example را Given/When/Then کن و برای هر Then مشاهدۀ مورد انتظار بنویس.
**خروجی مورد انتظار:** Ruleها مبهم نیستند، مثالها داده و نتیجه دارند، و هیچ Question مالی یا timezone پنهانی بهصورت test تصادفی نوشته نشده است.
**Recovery:** اگر Then میگوید «موفق شود»، status، تغییر قابل مشاهده یا عدم تغییر لازم را اضافه کن. اگر مثال implementation را نام میبرد، آن را با رفتار بیرونی جایگزین کن. اگر Question پاسخنداده مسیر اصلی را عوض میکند، به ledger و `DECIDE` برگرد.
## خطاهای رایج و نشانهها
- Rule و Example یکی شدهاند: «همپوشانی رد شود» دادهٔ نمونه ندارد.
- مثال بدون مرز: فقط مسیر موفق دیده شده و deadline/DST جا افتاده است.
- Given آلوده به implementation: test به جدول یا CSS وابسته است نه رفتار.
- EARS بهعنوان تضمین کیفیت: جمله ساختیافته است ولی آستانه یا authority ندارد.
- Questionهای باز در test: test به جای policy، پیشفرض پنهان را قفل میکند.
## چکلیست Verification
- [ ] کارت، Rule، Example، Question و Non-goal را جدا کردهام.
- [ ] Ruleها با زبان رفتاری/EARS نوشته شدهاند.
- [ ] هر Example داده، رخداد و نتیجهٔ قابل مشاهده دارد.
- [ ] Given/When/Then یک علت و انتظار اصلی دارد.
- [ ] timezone، لغو و همزمانی دستکم یک مثال مرزی دارند.
- [ ] سؤال blocking به ledger و owner برگشته، نه اینکه حدس زده شده باشد.
## تمرین مستقل
برای «بیمار reminder فردا را دوبار دریافت نکند» یک Example Map و دو Given/When/Then بنویس.
**Rubric پاسخ:** Rule باید اثر تکرار را روشن کند؛ مثالها retry و دو اجرای نزدیک را از هم جدا کنند؛ سؤالها timezone «فردا» و کانال پیام را ثبت کنند؛ Non-goal ارسال واقعی یا انتخاب provider باشد مگر Scope آن را افزوده باشد. پاسخ صرفاً «idempotent کنید» رفتار قابل مشاهده ندارد.
## جمعبندی
Example Mapping گفتوگو را به Artifact کوچک اما قابل اعتراض تبدیل میکند. EARS Rule را شفاف و Given/When/Then نتیجه را آزمونپذیر میکند. مثالهای timezone، deadline و double booking، کیفیت Spec را پیش از طراحی فنی میسنجند. فصل بعد این Artifactها را به Behavioral Spec و برنامهٔ اجرا پیوند میدهد.
## منابع مستقیم
- [Cucumber — Example Mapping](https://cucumber.io/blog/bdd/example-mapping-introduction/) — روش community برای Rule، Example و Question؛ جای تحلیل نیاز کامل نیست.
- [EARS requirements syntax](https://doi.org/10.1109/RE.2009.9) — مقالهٔ اصلی نحو ساختیافتهٔ نیاز.
- [Dan North — Introducing BDD](https://dannorth.net/blog/introducing-bdd/) — منبع بنیادی Given/When/Then؛ specification همهٔ ابزارها نیست.
- [Gojko Adzic — Specification by Example](https://gojko.net/books/specification-by-example/) — مرجع روششناختی؛ کیفیت مثال همچنان به authority و بازبینی نیاز دارد.
به زبان ساده
Example Mapping یک Rule کلی را به مثال دادهدار و سؤال باز تبدیل میکند. Given/When/Then وقتی مفید است که نتیجهٔ قابل مشاهده، عدمتغییر و مرزهای مهم را دقیق بیان کند.
مثال واقعی
در مثال فرضی `booking-platform`، بازهٔ ۱۰:۰۰ تا ۱۰:۳۰ و درخواست ۱۰:۱۵ تا ۱۰:۴۵ باید یک conflict قابل مشاهده بسازند؛ این از جملهٔ مبهم «reschedule درست کار کند» آزمونپذیرتر است.