۱۳۸۸/۰۲/۱۵

آشنايي با mocking frameworks (چارچوب‌هاي تقليد) - قسمت اول


اين مطلب در ادامه‌ي مطالب آزمو‌ن‌هاي واحد يا unit testing است.
نوشتن آزمون واحد براي كلاس‌هايي كه با يك سري از الگوريتم‌ها ، مسايل رياضي و امثال آن سر و كار دارند، ساده است. عموما اين نوع كلاس‌ها وابستگي خارجي آنچناني ندارند؛ اما در عمل كلاس‌هاي ما ممكن است وابستگي‌هاي خارجي بسياري پيدا كنند؛ براي مثال كار با ديتابيس، اتصال به يك وب سرويس، دريافت فايل از اينترنت، خواندن اطلاعات از انواع فايل‌ها و غيره.
مطابق اصول آزمايشات واحد، يك آزمون واحد خوب بايد ايزوله باشد. نبايد به مرزهاي سيستم‌هاي ديگر وارد شده و عملكرد سيستم‌هاي خارج از كلاس را بررسي كند.
اين مثال ساده را در نظر بگيريد:
فرض كنيد برنامه شما قرار است از يك وب سرويس ليستي از آدرس‌هاي IP يك كشور خاص را دريافت كند و در يك ديتابيس محلي آن‌ها را ذخيره نمايد. به صورت متداول اين كلاس بايد اتصالي را به وب سرويس گشوده و اطلاعات را دريافت كند و همچنين آن‌ها را خارج از مرز كلاس در يك ديتابيس ثبت كند. نوشتن آزمون واحد براي اين كلاس مطابق اصول مربوطه غير ممكن است. اگر كلاس آزمون واحد آن‌را تهيه نمائيد، اين آزمون، integration test نام خواهد گرفت زيرا از مرزهاي سيستم بايد عبور نمايد.

همچنين يك آزمون واحد بايد تا حد ممكن سريع باشد تا برنامه نويس از انجام آن بر روي يك پروژه بزرگ منصرف نگردد و ايجاد اين اتصالات در خارج از سيستم، بيشتر سبب كندي كار خواهند شد.

براي اين ايزوله سازي روش‌هاي مختلفي وجود دارند كه در ادامه به آن‌ها خواهيم پرداخت:

روش اول: استفاده از اينترفيس‌ها
با كمك يك اينترفيس مي‌توان مشخص كرد كه يك قطعه از كد "چه كاري" را قرار است انجام دهد؛ و نه اينكه "چگونه" بايد آن‌را به انجام رساند.
يك مثال ساده از خود دات نت فريم ورك، اينترفيس IComparable است:

public static string GetComparisonText(IComparable a, IComparable b)
{
if (a.CompareTo(b) == 1)
return "a is bigger";
if (a.CompareTo(b) == -1)
return "b is bigger";
return "same";
}
در اين مثال چون از IComparable استفاده شده، متد ما از هر نوع داده‌اي جهت مقايسه مي‌تواند استفاده كند. تنها موردي كه براي آن مهم خواهد بود اين است كه a راهي را براي مقايسه با b ارائه دهد.

اكنون با توجه به اين توضيحات، براي ايزوله كردن ارتباط با ديتابيس و وب سرويس در مثال فوق، مي‌توان اينترفيس‌هاي زير را تدارك ديد:
    public interface IEmailSource
{
IEnumerable<string> GetEmailAddresses();
}

public interface IEmailDataStore
{
void SaveEmailAddresses(IEnumerable<string> emailAddresses);

}
در اينجا استفاده و تعريف اينترفيس‌ها چندين خاصيت را به همراه خواهد داشت :
الف) به اين صورت تنها مشخص مي‌شود كه چه كاري را قصد داريم انجام دهيم و كاري به پياده سازي آن نداريم.
ب) ساخت كلاس بدون وجود يا دسترسي به يك ديتابيس ميسر مي‌شود. اين مورد خصوصا در يك كار تيمي كه قسمت‌هاي مختلف كار به صورت همزمان در حالت پيشرفت و تهيه است حائز اهميت مي‌شود.
ج) با توجه به اينكه در اينجا به پياده سازي توجهي نداريم، مي‌توان از اين اينترفيس‌ها جهت تقليد دنياي واقعي استفاده كنيم. (كه در اينجا mocking نام گرفته است)

جهت تقليد رفتار و عملكرد اين دو اينترفيس، به كلاس‌هاي تقليد زير خواهيم رسيد:

public class MockEmailSource : IEmailSource
{
public IEnumerable<string> EmailAddressesToReturn { get; set; }
public IEnumerable<string> GetEmailAddresses()
{
return EmailAddressesToReturn;
}
}

public class MockEmailDataStore : IEmailDataStore
{
public IEnumerable<string> SavedEmailAddresses { get; set; }
public void SaveEmailAddresses(IEnumerable<string> emailAddresses)
{
SavedEmailAddresses = emailAddresses;
}

}
تا اينجا اولين قدم در مورد ايزوله سازي كلاس‌هايي كه به مرز سيستم‌هاي ديگر وارد مي‌شوند، برداشته شد. اما به مرور زمان مديريت اين اينترفيس‌ها و افزودن رفتارهاي جديد به كلاس‌هاي مشتق شده از آن‌ها مشكل مي‌شود. به همين جهت تا حد ممكن از پياده سازي دستي آن‌ها خودداري شده و روش پيشنهادي استفاده از mocking frameworks است.

ادامه دارد ....