آموزش Unit Testing با استفاده از NUnit و Moq بخش دوم: Mocking

در این مقاله قصد داریم که به Mocking و کتابخانه محبوب Moq بپردازیم. https://vrgl.ir/rZMij در این مقاله از GitHub Gist استفاده شده است و لود شدن قسمت مربوط به کد ها ممکن است کمی زمانبر باشدتعریف Mock معمولا Unit Test ها روی یک Unit کار میکنند که به یک کلاس اطلاق می شود. گاهی اوقات این Unit یک سری Dependency هایی را همراه خود دارند که جزئی از تست نیستند ولی Unit مربوطه بدون این dependency ها نمی تواند کار کند.سناریویی را در نظر بگیرید که ClassA سیستم تحت تست می باشد ولی این کلاس در داخل خود از ClassB استفاده می کند. حال ما نیاز داریم که ClassB را در تست خود جایگزین کنیم و تنها ClassA را تست کنیم. در اینجا باید از Test Double ها استفاده کنیم. یکی از Test Double ها Mock می باشد. Mock در واقع یک Fake Object می باشد که میتواند مقدار تعریف شده را به عنوان Result خود برگرداند.همچنین یک Mock می تواند شامل اطلاعات اضافی مانند اینکه چه پارامتر هایی مورد استفاده قرار گرفته و چند بار یک Mock Object صدا زده شده را برگرداند.کتابخانه Moqیک کتابخانه بسیار کاربردی در زمینه Mocking می باشد که قابلیت های بسیاری از جمله شبیه سازی متد ها،Event ها، پراپرتی ها و... را در اختیارمان قرار میدهد.نصب Moqبه سراغ پروژه ای که در قسمت قبلی ایجاد کرده ایم می رویم. در Package Manager Console دستور زیر را برای نصب Moq اجرا میکنیم.Install-Package Moq -Version 4.16.1همچنین می توانیم از قسمت Manage Nuget Packages به دنبال Moq بگردیم و آن را نصب کنیم.شروع کار با Moqاینترفیس ILog زیر را در بگیرید. https://gist.github.com/babaktaremi/78fc50fa890eab51ca3a4030340de3c6 این اینترفیس یک متد ساده با نام WriteMessage دارد. حال کلاس BankAccount را در نظر بگیرید که به این اینترفیس وابستگی دارد. https://gist.github.com/babaktaremi/d48146695c884daa55521dfffbee4509 هدف ما تست یونیت BankAccount می باشد نه اینترفیس ILog. در اینجا کتابخانه Moq به کمک ما می آید. https://gist.github.com/babaktaremi/28cbe1a8d9040046f2548fc1a0a397c8 در اینجا در متد SetUp یک Mock Object از اینترفیس ILog ساخته ایم و آن را به BankAccount پاس داده ایم. بوسیله آن نیازمندی یونیت BankAccount به ILog تامین می شود و می توانیم یونیت BankAccount را بدون نیاز به ساخت اینترفیس ILog تست کنیم. در ادامه با سایر قابلیت های Moq آشنا می شویم.ایجاد Mocking Methodsاینترفیس IFoo زیر را در بگیرید. در ادامه مقاله قصد داریم که بدون نیاز به پیاده سازی این اینترفیس، آن را Mock کنیم. https://gist.github.com/babaktaremi/8ad3ff7140a15b0503703562bd6dbd71 ابتدا یک mock از اینترفیس IFoo می سازیم. https://gist.github.com/babaktaremi/b8a117e7070a130caa07e43f663f8154 کلاس mock یک متد با نام Setup در اختیارمان قرار میدهد که به وسیله آن میتوانیم رفتار mock را تغییر دهیم. رفتار متد DoSomething را به شکل زیر تعریف میکنیم https://gist.github.com/babaktaremi/7f90c024856b39df2441cd5b5e0d0497 خط 1 نشان میدهد که اگر متد هرکدام از مقدار Ping یا Foo را دریافت کرد False را به عنوان نتیجه بازگرداند و در خط 2 مقدار Pong توسط متد دریافت شد، مقدار True را بازگرداند. حال اگر متد DoSomething مقداری غیر از Ping، Pong و یا Foo را دریافت کند، نتیجه آن مقدار Default خروجی متد می باشد که در این حالت مقدار Default برای boolean مقدار False می باشد.بررسی Mocking برای متدهای وابسته به مقدار ورودیمتد Add از اینترفیس IFoo را در نظر بگیرید. فرض کنید که میخواهیم وقتی که عدد زوج است، مقدار True را برگردانیم و یا وقتی که ورودی در رنج اعداد 1 تا 10 بود مقدار False را برگردانیم. ویا برای متد DoSomething این شرط را داشته باشیم که ورودی String باید شامل حروف باشد.کلاس It از Moq شامل چند متد است که بوسیله آن میتوانیم برای خروجی متد ها شرط بگذاریم. https://gist.github.com/babaktaremi/f1f1580d4a17a8694556f1bbb1d092e4 بررسی Mocking برای پارامترهای Ref و Outبرای Mock کردن پارامتر out در Moq می توانیم به شکل زیر عمل کنیم https://gist.github.com/babaktaremi/3f82e1adad045defcc16200d14fb2d49 دقت کنید که در متد بالا تعریف کرده ایم که اگر ورودی TryParse مقدار Ping بود،خروجی متد مقدار True داشته باشد و out Argument را به مقدار ok تطبیق دهد.برای Ref میتوانیم مانند زیر عمل کنیم.دقت داشته باشید که در مورد Ref کتابخانه Moq به صورت Reference Equality عمل میکند و Reference دو آبجکت را بررسی میکند. https://gist.github.com/babaktaremi/0b3acd4a18cdcfad45765616ecb8df4d بررسی مقدار بازگشتی برای Mocking متدهاهنگامی که مقدار بازگشتی متد باید دارای مقدار خاصی باشد میتوانیم از یک Lambda Expression برای خروجی مقدار Returns استفاده کنیم. به طور مثال مقدار ProcessString در Mock زیر همواره کاراکتر ها را lower کرده و برمیگرداند. https://gist.github.com/babaktaremi/1c596e7998d3cf17526575a4ad4155e0 بررسی CallBack در Moqموقع Setup کردن یک متد میتوانیم تعریف کنیم که به ازای هر Call مقدار های مختلف داشته باشیم. مثلا میتوانیم تعریف کنیم که هربار متد Call شد یک Counter مقدارش زیاد شود که میتوانیم از CallBack به شکل زیر استفاده کنیم. در واقع CallBack یک فانکشن است که هربار که متد فراخوانی میشود،مقدار تعریف شده در CallBack اجرا می شود. https://gist.github.com/babaktaremi/b3944e368cc592a7dfd5cb9655662d13 بررسی Exception ها در Moqبرای اینکه یک Exception را در Moq شبیه سازی کنیم میتوانیم به صورت زیر عمل کنیم. https://gist.github.com/babaktaremi/b1a2af54ce727cbbd232378034284798 بررسی Property Mockingدر mock Object نمیتوان که مقداری را به یک Property تخصیص داد چرا که در اصل هیج Object ای وجود ندارد. میتوانیم به صورت زیر یک مقدار را به یک پراپرتی Assign کنیم. https://gist.github.com/babaktaremi/bb50ba21886bdc31a4613ab472d05af7 در مورد Setter های یک پراپرتی می توانیم مانند زیر عمل کنیم و هربار که Setter یک پراپرتی فراخوانی شد، عملیاتی را انجام دهیم. https://gist.github.com/babaktaremi/4cfe85e79d84e750a47fd071e72db145 بررسی Value Tracking پراپرتی هامیتوانیم به شکل زیر، تمامی پراپرتی های موجود در mock را طوری تنظیم کنیم که رفتاری مانند یک پراپرتی داشته باشند. https://gist.github.com/babaktaremi/80c636a800f2f88f8b5c33d33ef873b0 در خط 5 تمامی پراپرتی های ابجکت mock را تنظیم میکنیم که رفتاری مانند پراپرتی داشته باشند.بررسی Event Mockingفرض کنید که اینترفیس زیر را به عنوان نمونه داریم که دارای یک Event و یک Delegate می باشد. https://gist.github.com/babaktaremi/631674a96e43b84a752ec769b0191f26 https://gist.github.com/babaktaremi/c584293b5b681ba58b9e687f3728d6a0 حال یک کلاس با نام Doctor به شکل زیر ایجاد میکنیم که وابستگی به اینترفیس IAnimal دارد و به Event ها Subscribe کرده است. https://gist.github.com/babaktaremi/003860bdfed6be1a6783f4562ae997bf برای Raise کردن یک Event می توانیم مانند زیر عمل کنیم. https://gist.github.com/babaktaremi/deafd22ffda5388fbdcf2b637768c7db میتوانیم در SetUp یک متد، Raise کردن یک Event را در نظر بگیریم. https://gist.github.com/babaktaremi/36591474d085d8ef25a787734ded5918 میتوانیم یک Custom Event داشته باشیم. به طور مثال در اینترفیس IAnimal یک delegate داریم که میتوانیم از آن به شکل زیر در mock object استفاده کنیم. https://gist.github.com/babaktaremi/a2593a33ea878ff133eb6cc97b918c00 بررسی دسترسی به Protected Members در Mockفرض کنید که کلاس Person را به شکل زیر داریم که دارای اعضای Protected می باشد. https://gist.github.com/babaktaremi/8f6adf19e6344accd7c4712c942cc437 میتوانیم به شکل زیر به اعضای Protected دسترسی داشته باشیم و همچنین متدهای Protected را Invoke کنیم. https://gist.github.com/babaktaremi/f096765cd7a2681ac7bacb5e0599ea5e دقت داشته باشید که که برای Invoke کردن متد به جای استفاده از دستور It باید از دستور ItExpr استفاده کنیم.نتیجه گیریدر این مقاله با Mocking و کتابخانه Moq آشنا شدیم. به طور کلی کتابخانه Moq ابزاری فوق العاده قدرتمند برای شبیه سازی بخش هایی از یونیت که مورد نظر تست نیستند می باشد. اگر به کد های این بخش نیاز داشتید میتوانید آن را از گیت هاب به لینک زیر دریافت کنید. خوشحال میشوم که نظرات شما را راجب این مقاله بدانم و همچنین اگر دوست داشتید میتوانید من را به یک قهوه مهمان کنید. https://coffeebede.ir/buycoffee/bobby https://github.com/babaktaremi/NUnit-Drafts مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom