۳۰ روز با TDD: روز شانزدهم- استفاده از پارامترهای مشخص در Stub ها

روز شانزدهم از مجموعه نوشته‌های ۳۰ روز با توسعه آزمون محور

ما مثال چند نوشته قبلی این سری نوشته‌ها را پی خواهیم گرفت. نیازمندی نرم‌افزاری را در روز دوازدهم از Stub بیان کردیم. به عنوان بخشی از این نیازمندی، ما باید با یک سیستم ثبت سفارش خارجی ارتباط برقرار کنیم. تست کیس بعدی که می‌خواهم به آن بپردازم مربوط به همین ارتباط با سیستم خارجی ثبت سفارش است. به جای اینکه خودمان بخش ثبت سفارش را بنویسیم، مدیریت تصمیم گرفته که آن را برون‌سپاری (outsource) کنیم. این تصمیم به آن معنی است که برای بخش ثبت سفارش باید با یک وب سرویس خارج از نرم‌افزار اصلی ارتباط برقرار کنیم. همچنین برای این ارتباط باید بخشی از اطلاعات مشتری (حداقل نام و آدرس) را آماده کنیم.

برای ارائه اطلاعات مشتری (اعم از نام و آدرس) CustomerService می‌بایست تغییر کند.

سپس به یک Interface برای Customer Service نیاز داریم

هم تعریف Entity و هم Interface مربوط به Customer Service را به پروژه TddStore.Core اضافه می‌کنیم.

در این قسمت باید از اولین تست کیس مربوط به تعامل با OrderFulfillment Service صحبت کنم که جالب است بدانید هیچ ارتباطی با خود OrderFulfillment ندارد! در این مورد می‌خواهم مطمئن شوم که OrderService به درستی از CustomerService استفاده می‌کند.

صورت مساله تست: وقتی یک مشتری معتبر سفارشی را ثبت می‌کند (که اعتبارسنجی آن انجام شده) این سفارش ثبت شده و یک شناسه سفارش برگردانده می‌شود

به یک تست جدید نیاز دارم:

بلی! این شبیه شروع تست WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned  به نظر می‌رسد و خیر! نباید WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned را تغییر دهیم که در کنار تست اصلی، تعامل با CustomService را نیز تست کند.

هدف تست اصلی این است که تعامل با OrderDataSevice به درستی کار می‌کند. جزئیات این تعامل بسته به اینکه متد چطور با CustomerService کار می‌کند ممکن است متفاوت باشد. با تکامل این تست‌ها، نیاز به mock‌های بیشتر OrderDataService حس می‌شود. این باعث می‌شود که هر دو تعامل (از OrderService به OrderDataService و از OrderService به CustomerService) تقریباً مستقل از هم تغییر کنند که باعث تولید تست‌های شکننده کمتری می‌شود. همچنین با استفاده از این روش، چنانچه تستی fail شود دقیقاً خواهم دانست که آیا ارتباط بین OrderDataService و CustomerService باعث این fail شده یا خیر.

قبل از اینکه جلوتر برویم، لازم است وهله (instance) از mock مربوط به CustomerService را در کلاس OrderServiceTests معرفی کنم. همچنین باید از SetupTestFixture نیز استفاده کنم.

می‌دانم که برای نیازمندی مطرح شده در OrderFultillment باید مشتری را از CustomerService بازیابی (retrieve) کنم. در حال حاضر mock مربوط به CustomerService را تعریف کرده‌ام، پس حالا باید arrange انجام شود. در این مورد می‌خواهم stub من یک مشتری مشخص را وقتی که GetCustomer با پارامتر خاصی فراخوانی می‌شود برگرداند.

در ایجاد شی customerToReturn من شی Customer را با Id و نام و نام خانوادگی پر کردم. از نظر فنی در حال حاضر نیازی به نام و نام خانوادگی در این شی ندارم، اما می‌خواهم تا جای ممکن فیلدهای بیشتری را برای تست پر کنم. حالا که ورودی و خروجی تعریف شده‌اند، می‌توانم stub را arrange کنم. در این مورد فقط می‌خوام تا stub مربوط به CustomerService مقدار cutsomterToReturn را وقتی که GetCustomer با پارامتر CustomerId فراخوانی می‌شود برگرداند. همچنین انتظار دارم این متد تنها یک بار فراخوانی شود

در پایان لازم است فراخوانی صحیح mock جدید را با assert چک کنم. این کار را با فراخوانی Mock.Assert و پاس دادن mock مربوط به CustomerService می‌توانم انجام بدهم.

همانطور که انتظار داریم تست هنگام اجرا fail می‌شود

کد stub‌ مربوط به CustomerService من فراخوانی نشده. باید به متد PlaceOrder در کلاس OrderService بروم و منطق فراخوانی CustomerService را پیاده سازی کنم.

در این قسمت نمی‌توانم کدم را اجرا کنم چون کامپایل نمی‌شود. ارجاع (reference) به CustomerService در کلاس OrderService ندارم. پس گام بعدی تغییر OrderService برای پذیرش ICustomerService به عنوان یک وابستگی است که از طریق سازنده (constructor) آن را تزریق می‌کنم (برای این تغییر کد راحت‌تر باشد متد PlaceOrder را از نمونه کد زیر برداشتم)

اجرای مجدد تست‌ها نشان می‌دهد که کد همچنان کامپایل نمی‌شود. دلیلش این است که در متد SetupTestFixture کلاس OrderServiceTests تلاش می‌کنیم وهله (instance) از OrderService بدون پاس دادن ICustomerService به سازنده (constrcutor) کلاس بسازیم. برای این کار متد را برای تامین mock در سازنده OrderService تغییر دادم (برای مشاهده راحت‌تر تغییر کد، using ها و متدهای تست را از کلاس برداشتم)

حالا تست آماده اجراست و همانطور که در تصویر زیر مشخص است، pass می‌شود.

چیزی که ممکن است سوال کنید این است که چرا مشابه تست اصلی، نتیجه فراخوانی PlaceOrder را دریافت و آزمایش نکردیم. دلیلش این است که در این تست، ما اهمیتی به شماره سفارش نمی‌دهیم و فقط تعامل با CustomerService را مورد آزمایش قرار می‌دهیم.

ادامه دارد….