‫Mocking و Rhino Mocks

از همان اولین باری که اسم Mock و Mocking را شنیدم حس کردم چیز گنگی است و تا لازم نشده سراغ آن نروم. اما به تازگی فهمیدم که اولاً با Mocking بعضی unit testهای غیر ممکن، ممکن می‌شوند، ثانیاً استفاده از آن می‌تواند تست نرم‌افزار را خیلی راحت‌تر و شیرین‌تر کند. قاعدتاً همه اسم Mocking را شنیده‌اند و تا حدودی می‌دانند به چه در می‌خورد و حتی شاید مثال‌هایی را از آن دیده باشند. اگر واقعاً چیزی از آن نمی‌دانید به این مطلب و این مطلب از وحید مراجعه کنید.

مشکل من با Mocking از آنجا بود که نمی‌دانستم از کجا باید شروع کنم و چه چیزی را باید یاد بگیرم. من تمام وبلاگ/سایت نویسنده Rhino Mocks که تبدیل به استانداردی در دنیای Mocking شده را زیر و رو کردم، همینطور StackOverflow و گوگل را. اما ظاهراً خیلی از مطالبی که پیدا می‌کردم از جمله مطالب خود وبلاگ/سایت نویسنده آن یعنی Ayende Rahien از تاریخ گذشته و اصطلاحاً depricate شده بودند یا این که فهمیدن آنها خیلی سخت بود. هر کسی از هر روشی که خوشش آمده بود استفاده کرده بود و هیچ منبع درست و حسابی پیدا نمی‌شد. خوشبختانه با همه بالا و پایین‌ها متوجه شدم که دو روش کلی برای انجام Mocking با Rhino Mocks وجود دارد:

۱- روش کلاسیک یا Record-Replay: این روش قدیمی‌تر است و مبتنی بر انجام یک سری عملیات پشت سر هم، ضبط و سپس تکرار آنهاست. بیشتر منابعی که در اینترنت پیدا کرده بودم از این روش استفاده می‌کردند.

۲- روش AAA: این روش بعد از ورود lambda expressionها و extension methodها به C#‎ ابداع و مورد استفاده قرار گرفته است. بیشتر افراد معتقد هستند این روش خیلی راحت‌تر و خواناتر است. من هم این روش را برای ادامه کارم انتخاب کرده‌ام. یک مطلب بسیار خوب در مورد یادگیری این روش در اینجا آمده است.

برای استفاده از Mocking در تست‌ها باید آماده برداشتن یک قدم مهم بود: استفاده از مفاهیم Decoupling و Dependency Injection در سطح کد یا حداقل استفاده از کلاس‌های Virtual و غیر Static. چون Rhino Mock و بقیه ابزارها یا فقط از interfaceها یا از کلاس‌هایی که متودشان به صورت virtual تعریف شده باشد می‌توانند استفاده کنند. دلیل آن هم اصلاً غیر منطقی نیست. به یاد داشته باشید برای استفاده از Lazy Loading در NHibernate هم باید setterها و getterها به صورت Virtual تعریف می‌شد.

چند منبع:
۱-  مطلب اول و دوم وحید
۲- صفحه‌ی رسمی Rhino Mocks
۳- وبلاگ پدیدآورنده‌ی Rhino Mocks
۴- نوشته‌ی مارتین فولر درباره فرق Stub و Mock
۵- راهنمای خیلی خوبی برای شروع Mocking با استفاده از روش AAA
۶- صفحه‌ی ویکی‌پدیا درباره‌ی Mocking
۷- توضیحات پدیدآورنده‌ی Rhino Mocks درباره روش AAA

‫کمی درباره‌ی Mock و Stub

هم Mock و هم Stub در unit testهایی استفاده می‌شوند که شخص نمی‌تواند یا نمی‌خواهد از بعضی objectهای مورد نیاز استفاده نماید. مثلاً قرار است متود ارسال ایمیل به امور فروش در صورت کاهش موجودی برخی کالاها تست شود. فرض کنید موقع تست امکان ارسال ایمیل به علت قطعی اینترنت وجود ندارد. در این حالت آبجکت مربوط به ارسال ایمیل به نوعی شبیه‌سازی می‌شود. این کار باعث می‌شود تست مورد نظر روی خود عملیات کاهش موجودی و ارسال ایمیل تمرکز کند و کاری به مشکلات مربوط به ارسال ایمیل نداشته باشد. به غیر از ارسال ایمیل حالات دیگری هم وجود دارند که در آن استفاده از object مورد نظر امکان‌پذیر نیست. مثلاً خواندن اطلاعات از یک device وقتی که خود device را نداریم، محاسبه حقوق بر اساس اطلاعات کارمندان یک شرکت وقتی که اطلاعات هیچ کدام از کارمندان را نداریم و بسیاری حالات دیگر. خود شبیه‌سازی هم صرفاً محدود به Mock و Stub نمی‌شود. مثلاً می‌توان از الگوهایی مثل Object Mother هم استفاده کرد.

Mock با استفاده از برنامه‌های کمکی که به اسم Isolation Framework یا Mocking Framework معروف هستند انجام می‌شود. Rhino Mocks معروف‌ترین و پراستفاده‌ترین Mock Framework در دات‌نت است. در روش Mock ما آن آبجکتی را که قرار است شبیه‌سازی شود را معرفی می‌کنیم و سپس انتظاراتمان را از آن بیان می‌کنیم. مثلاً فرض کنید آبجکتی به اسم FaxReceiver دارید که یکی از متودهایش وظیفه دارد به شما بگوید از تاریخ آخرین عوض کردن کاغذ دستگاه فاکس، چند عدد فاکس دریافت کرده است. متود مورد تست شما از این آبجکت استفاده می‌کند و هر وقت که تعداد فاکس‌های دریافتی بیش از هزار عدد شد به اپراتور دستگاه پیغام دهد که موقع عوض کردن کاغذ دستگاه فرا رسیده است. با روش‌های معمولی تست نمی‌توان این متود را تست کرد مگر این که هزار عدد فاکس دریافت کنید. در اینجاست که از روش‌های شبیه‌سازی مثل Mock استفاده می‌شود. برای این کار آبجکت FaxReceiver به Mock Framework معرفی شده و به او می‌گوییم هر وقت متود تعداد فاکس‌های دریافتی فراخوانی شد از او «انتظار» داریم مقدار هزار را برگرداند.

مثال بالا را می‌توان با Stub هم انجام داد. با این تفاوت که در مورد Stub هیچ Framework یا Library وجود ندارد و شما مجبور هستید همه یا بخشی از آبجکت/متود مورد نظر را خودتان بنویسید. مثلا متودی بنویسید که به جای مراجعه واقعی به دستگاه فاکس و خواندن تعداد فاکس‌های دریافتی از آن، همیشه عدد هزار را برگرداند. با این که کار با Stub خیلی سخت‌تر از کار با Mock است ولی بعضی حالات وجود دارند که فقط با Stub می‌شود آنها را پیاده‌سازی کرد. مثلاً وقتی که نگهداشتن State برای ما مهم باشد. به عنوان مثال فرض کنید در تست دیگری بخواهیم بدانیم چند بار متود فراخوانی تعداد فاکس‌های دریافتی فراخوانی شده است. انجام این کار با Mocking امکان‌پذیر نیست و باید از روش Stub استفاده شود.

منابع:
Mocks Aren’t Stubs
Mock object
آشنايي با mocking frameworks (چارچوب‌هاي تقليد) – قسمت اول
آشنايي با mocking frameworks – قسمت دوم