مقدمه:
از آنجایی که در این سایت در مورد shim و stub صحبتی نشده دوست داشتم مطلبی در این باره بزارم. در آزمون واحد ما نیاز داریم که یک سری اشیا را moq کنیم تا بتوانیم آزمون واحد را به درستی انجام دهیم. ما در آزمون واحد نباید وابستگی به لایههای پایین یا بالا داشته باشیم پس باید مقلدی از object هایی که در سطوح مختلف قرار دارند بسازیم.شاید برای کسانی که با آزمون واحد کار کردند، به ویژه با فریم ورک تست Microsoft، یک سری مشکلاتی با mock کردن اشیا با استفاده از Mock داشته اند که حالا میخواهیم با معرفی فریم ورکهای جدید، این مشکل را حل کنیم.
برای اینکه شما آزمون واحد درستی داشته باشید باید کارهای زیر را انجام دهید:
1- هر objectی که نیاز به mock کردن دارد باید حتما یا non-static باشد، یا اینترفیس داشته باشد.
2- شما احتیاج به یک فریم ورک تزریق وابستگیها دارید که به عنوان بخشی از معماری نرم افزار یا الگوهای مناسب شیءگرایی مطرح است، تا عمل تزریق وابستگیها را انجام دهید.
3- ساختارها باید برای تزریق وابستگی در اینترفیسهای objectهای وابسته تغییر یابند.
Shims و Stubs:
نوع stub همانند فریم ورک mock میباشد که برای مقلد ساختن اینترفیسها و کلاسهای non-sealed virtual یا ویژگی ها، رویدادها و متدهای abstract استفاده میشود. نوع shim میتواند کارهایی که stub نمیتواند بکند انجام دهد یعنی برای مقلد ساختن کلاسهای static یا متدهای non-overridable استفاده میشود. با مثالهای زیر میتوانید با کارایی بیشتر shim و stub آشنا شوید.
یک پروژه mvc ایجاد کنید و نام آن را FakingExample بگذارید. در این پروژه کلاسی با نام CartToShim به صورت زیر ایجاد کنید:
namespace FakingExample { public class CartToShim { public int CartId { get; private set; } public int UserId { get; private set; } private List<CartItem> _cartItems = new List<CartItem>(); public ReadOnlyCollection<CartItem> CartItems { get; private set; } public DateTime CreateDateTime { get; private set; } public CartToShim(int cartId, int userId) { CartId = cartId; UserId = userId; CreateDateTime = DateTime.Now; CartItems = new ReadOnlyCollection<CartItem>(_cartItems); } public void AddCartItem(int productId) { var cartItemId = DataAccessLayer.SaveCartItem(CartId, productId); _cartItems.Add(new CartItem(cartItemId, productId)); } } }
public class CartItem { public int CartItemId { get; private set; } public int ProductId { get; private set; } public CartItem(int cartItemId, int productId) { CartItemId = cartItemId; ProductId = productId; } }
اگر بر روی فایل fakes که در reference ایجاد شده دوبار کلیک کنید میتوانید کلاسهای CartItem و CartToShim را مشاهده کنید که هم نوع stub شان است و هم نوع shim آنها که در تصویر زیر میتوانید مشاهده کنید.
ShimDataAccessLayer را که مشاهده میکنید یک متد SaveCartItem دارد که به دیتابیس متصل شده و آیتمهای کارت را ذخیره میکند.
حالا میتوانیم تست خود را بنویسیم. در زیر یک نمونه از تست را مشاهده میکنید:
[TestMethod] public void AddCartItem_GivenCartAndProduct_ThenProductShouldBeAddedToCart() { //Create a context to scope and cleanup shims using (ShimsContext.Create()) { int cartItemId = 42, cartId = 1, userId = 33, productId = 777; //Shim SaveCartItem rerouting it to a delegate which //always returns cartItemId Fakes.ShimDataAccessLayer.SaveCartItemInt32Int32 = (c, p) => cartItemId; var cart = new CartToShim(cartId, userId); cart.AddCartItem(productId); Assert.AreEqual(cartId, cart.CartItems.Count); var cartItem = cart.CartItems[0]; Assert.AreEqual(cartItemId, cartItem.CartItemId); Assert.AreEqual(productId, cartItem.ProductId); } }
به این نکته توجه داشته باشید که حتما برای assert کردن باید assertها را در داخل اسکوپ ShimsContext قرار گرفته باشد در غیر این صورت assert شما درست کار نمیکند.
این یک مثال از shim بود؛ حالا میخواهم مثالی از یک stub را برای شما بزنم. یک اینترفیس با نام ICartSaver به صورت زیر ایجاد کنید:
public interface ICartSaver { int SaveCartItem(int cartId, int productId); }
public class CartSaver : ICartSaver { public int SaveCartItem(int cartId, int productId) { using (var conn = new SqlConnection("RandomSqlConnectionString")) { var cmd = new SqlCommand("InsCartItem", conn); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@CartId", cartId); cmd.Parameters.AddWithValue("@ProductId", productId); conn.Open(); return (int)cmd.ExecuteScalar(); } } }
[TestMethod] public void AddCartItem_GivenCartAndProduct_ThenProductShouldBeAddedToCart() { int cartItemId = 42, cartId = 1, userId = 33, productId = 777; //Stub ICartSaver and customize the behavior via a //delegate, ro return cartItemId var cartSaver = new Fakes.StubICartSaver(); cartSaver.SaveCartItemInt32Int32 = (c, p) => cartItemId; var cart = new CartToStub(cartId, userId, cartSaver); cart.AddCartItem(productId); Assert.AreEqual(cartId, cart.CartItems.Count); var cartItem = cart.CartItems[0]; Assert.AreEqual(cartItemId, cartItem.CartItemId); Assert.AreEqual(productId, cartItem.ProductId); }