آموزش 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