۳۰ روز با TDD: روز پانزدهم - ساده همیشه به معنی واضح نیست قسمت دوم

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

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

مشکل کجاست؟

در روز چهاردهم این تست کیس را دریافت کردیم:

وقتی کاربری سفارشی به تعداد صفر می‌دهد، خطایی از جنس InvalidOrderException می‌بایست برگردد.

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

تست pass‌ شد، اما هنوز یک مشکل وجود دارد. می‌خواهیم مطمئن شویم که متد PlaceOrder در کلاس OrderService یک InvalidOrderException ایجاد می‌کند و متد Save کلاس OrderDataService را فراخوانی نخواهد کرد. به نظر می‌رسد تست این اعتبارسنجی را انجام می‌دهد. ما به تست گفته‌ایم که انتظار InvalidOrderException داشته باشد و stub را طوری تعریف کرده‌ایم که متد Save فراخوانی نشود. تست pass می‌شود، پس مشکل کجاست؟

مشکل اینجاست که تست pass می‌شود، اما فراخوانی Mock.Assert هرگز اتفاق نمی‌افتد. باور نمی‌کنید؟ بیایید یک آزمایش انجام دهیم. بیایید فراخوانی OccursNever را به OccursOnce تغییر دهیم.

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

به نظر می‌رسد تست fail نشد. مشخصاً Mock.Assert فراخوانی نمی‌شود.

مشخص شد که مشکل از NUnit است یا به عبارت بهتر از خاصیت ExceptedException در NUnit. این خاصیت به تست می‌گوید که کل کد تست را درون یک بلاک try/catch قرار دهد و یک نوع خاص exception را بگیرد. فراخوانی PlaceOrder در خط ۱۶ باعث بروز exception می‌شود. از آنجایی که ما این exception را handle نکرده‌ایم به سطح اجرا کننده تست (NUnit) می‌رود. به محض اینکه اجرا کننده تست exception ای که منتظرش بوده را دریافت کند تست را pass می‌کند و هیچ خط کد دیگری بعد از بروز exception را اجرا نمی‌کند.

در بسیاری از موارد استفاده از ExpectedException خوب است. ما حالا به موقعیتی برخورد کردیم که استفاده از این خاصیت خوب نیست پس باید به دنبال راه جایگزین و دستی‌تری باشیم. اولین چیزی که لازم است انجام دهم، حذف ExpectedException از کد است. سپس باید فراخوانی PlaceOrder را در یک try/catch قرار بدهم.

برای تایید کد من نوع خاص InvalidOrderExpection را در catch متد PlaceOrder می‌گیرم. بعد از گرفتن این exception نوبت به Mock.Assert می‌رسد. اگر این کار کند، باید به اجرا کننده تست بگویم که تست pass شده است پس از Assert.Pass استفاده می‌کنم. اگر نوع دیگری از exception ایجاد شد یا کد خطا نداد Assert.Fail باعث fail شدن تست می‌شود.

تست را اجرا می‌کنیم تا ببینیم fail می‌شود یا خیر؟

این fail به این خاطر است که setup مربوط به stub ما انتظار دارد که متد Save ذقیقاً یک بار اجرا شود. این را عمداً اصلاح نکردم تا fail شدن تست را ببینید. حالا کد را تغییر می‌دهیم تا مطمئن شویم متد Save هرگز فراخوانی نمی‌شود:

تست را دوباره اجرا می‌کنیم و می‌بینیم تست pass می‌شود.

ادامه دارد…