Unit tests and DRY

One of common things that I unit test with NUnit is testing throwing or not throwing a specific exception. This is done using Assert.Throws and Assert.DoesNotThrow. I used to write 2 method for a exception assert unit test. One that do the actual work and one for unit test previous method. Consider following:

pubic void method1()
{
int a =1;
int b=2;
//plenty setup needed here

MyClass.DoSomething(a, b);
}

[Test]
public void DoSomethingTest()
{
Assert.DoesNotThrow(method1);
}

Now suppose that you have to unit test MyClass.DoSomething against many similar inputs. One way to do is repeat method1 and DoSomethingTest as needed. A disadvantage is that it’s against DRY principle. As Assert.DoesNotThrow ony accepts methods that has not parameter, I couldn’t stop to repeat initialization/setup codes in every method.

Fortunately today I found that I can use Anonymous Methods with Assert.Throws and Assert.DoesNotThrow without need to create several methods. Using this way DRY is leveraged in unit tests. See following example:

[Test]
public void DoSomethingTest()
{
int a1 = 1;
int b1 =2;

int b2 =4;

int b3 = -1;

Assert.DoesNotThrows(delegate()
{
MyClass.DoSomething(a1, b1);
});

Assert.DoesNotThrows(delegate()
{
MyClass.DoSomething(a1, b2);
});

Assert.DoesNotThrows(delegate()
{
MyClass.DoSomething(a1, b3);
});

Note: Many others have problems with applying DRY or KISS with unit tests including this one.

مشکل ارتقای اجزای نرم‌افزار

یکی از مشکلات بزرگی که نرم‌افزار نویس‌ها مدام با آن دسته و پنجه نرم می‌کنند مشکل ارتقا اجزا و بخش‌های مختلف یک نرم‌افزار است. برای این مشکل مثال‌های زیادی وجود دارند:
۱- برنامه را با VB6 نوشته‌اید ولی حالا که به VB.NET ارتقا داده‌اید متوجه شده‌اید که پیش‌فرض VB.NET برای اعضای کلاس private است نه public.
۲- برنامه را با NHibernate 1.2 نوشته‌اید ولی بعد از ارتقا به NHibernate 2.0 فهمیده‌اید که برنامه با آن configuration قدیمی کار نمی‌کند و باید web.config/app.config را هم عوض کنید.
۳- برنامه را با فلان library نوشته‌اید و بعد از ارتقا به نسخه جدید فهمیده‌اید که نسخه جدید به خیلی از مسائلی که قبلاً گیر نمی‌داد حالا گیر می‌دهد و نمی‌گذارد برنامه اجرا شود.
۴- در برنامه‌های ASP.NET از کنترل‌های Telerik استفاده کرده‌اید و بعد از ارتقا فهمیدید که tagهای استفاده شده در markup اسمشان عوض شده و حالا دچار parser error شده‌اید.
۵- در برنامه‌ای که برای ویندوز ۹۵ نوشته شده از یک API خاص استفاده شده که دیگر در ویندوزهای جدیدتر وجود ندارد.
۶- در بخشی از برنامه از نسخه x یک library استفاده کرده‌اید و بعداً لازم می‌شود بخشی از یک نرم‌افزار دیگر را به نرم‌افزار فعلی‌تان الحاق کنید اما این بخش جدید از نسخه y استفاده می‌کند نه x.
۷-…

این مسئله که برای برنامه‌نویسان عذاب‌آور و برای مدیران ترس‌آور است را می‌توان به دو بخش خارجی و داخلی تقسیم کرد. بخش خارجی مربوط است به dllها، libraryها، APIها و platformهایی که از خارج شرکت/تیم می‌آیند و معمولاً حق انتخاب زیادی در مورد آنها وجود ندارد. بخش داخلی مربوط است به اجزا و dllهایی که در داخل شرکت ساخته می‌شود و version زدن آنها یک مصیبت بزرگ است. در مورد dllهای داخلی مسئله backward comaptiblity و وابستگی به dllهای دیگر هم باید چک شود. چیزی که معمولاً در مورد dllهای خارجی بهتر رعایت می‌شود.

برای کاهش مشکلات ارتقا به نظر بنده موارد زیر را می‌توان در نظر گرفت:
۱- مثبت‌نگر باشید. ارتقا dllها خصوصاً در مورد dllهای خارجی معمولاً نوید دهنده امکانات بهتر و کامل‌تر است. معمولاً هدف از ارائه نسخه جدیدتر رفع اشکالات نسخ قبلی، بهبود عملکرد و… است. پس بنابراین خوشحال باشید که رفع مشکلات مربوط به نسخه جدید به معنی ارتقا کیفیت نرم‌افزار شما خواهد بود.
۲- پیروی از اصل KISS (Keep It Simple Stupid)‎: یعنی تا آنجا که می‌توانید برنامه را ساده نگه داشته و بیخودی از هر کتابخانه، class و dll دیگری استفاده نکنید.
۳- Dependency Injection: برنامه‌ها با حداقل وابستگی به هم نوشته شوند.
۴- موتوا قبل ان تموتوا (بمیرید قبل از آن که بمیرید): قبل از آن که مجبور شوید ارتقا دهید، ارتقا دهید. تولید کنندگان نرم‌افزار معمولاً قبل از ارائه نهایی نسخ جدید آنها را به صورت نسخه‌های آزمایشی ارائه می‌دهند و حتی قبل از آن کلی بحث راه می‌اندازند، نظر سنجی و اطلاع‌رسانی می‌کنند که ما قرار است فلان چیز را به نرم‌افزارمان اضافه کنیم یا تغییر بدهیم. بنابراین فرصت خیلی زیادی دارید که خود را به موقع ارتقا دهید.
۵- پیروی از تجارب TDD مثل Unit Test و Continuous integration: این راه‌حل خصوصاً در مورد dllهای داخلی خیلی خوب جواب می‌دهد. اگر برای همه بخش‌هایی ممکن unit test نوشته باشید آن وقت نگرانی کمتری برای ارتقا دارید چون خیلی سریع می‌فهیمد که آیا چیزی خراب شده یا نه. Continuous integration هم به همین شکل کمک می‌کند که در حداقل زمان ممکن خطا را کشف کنید.
۶- به هیچ وجه Warningهای هنگام کامپایل را دستکم نگیرید. خیلی از warningهای نسخه امروز به compile errorهای نسخه فردا تبدیل خواهند شد.
۷- استفاده از الگوهای جدیدی مثل MVC چون کار تست را راحت‌تر کرده و decoupling که با استفاده از dependency injection محقق می‌شود را بهتر اجرا می‌کنند.

شما چه راه‌هایی برای کاهش این طور مشکلات سراغ دارید؟

‫چطور یک attribute ناقابل ده روز سرکارم گذاشت!

چند وقت پیش یک پروژه تستی کوچک NHibernateی ایجاد کردم برای ذخیره و بازیابی چند کلاس ساده. یک پروژه تست هم با استفاده NUnit درست کردم برای تست آن. در تست کردن به یک خطای آزار دهنده برخورد کرده بودم. طبق معمول از سر بی‌دقتی فقط آخر پیغام خطا را می‌خواندم که گفته بود don’t flush the Session after an exception occurs. به همین خاطر به شدت به مدیریت session مربوط به NHibernate در متودهای [SetUp] و [TearDown] مشکوک شده بودم. خیلی آن را بالا و پایین کردم و کلی آن را با نمونه کدهای قبلی مقایسه کردم. آخرش متقاعد شدم یا ایراد از همان دو متود Setup و TearDown است یا به این خاطر است که این بار از روش جدیدی برای ایجاد پروژه‌ی اصلی استفاده کرده بودم.

 توی این هیری ویری هم هاردم سوخت و پروژه یک هفته خوابید. تا این که امشب بعد از کلی دقت به پیغام خطا فهمیدم که پیغام خطا با not-null property references a null or transient شروع می‌شود. البته جستجوی این خطا هم در اینترنت باز هم بیشتر مرا گمراه کرد. نمی‌دانم در «یک لحظه» چه اتفاقی افتاد که به mapping کلاس مربوطه شک کردم و نگاهی به آن انداختم. حدستان درست است، علت خطا در mapping نهفته بود. در تعریف همه propertyها یک attribute وجود داشت به اسم not-null که برابر true قرار داده شده بود. در واقع چیزی که برای من اتفاق افتاده بود خطا نبود. بلکه خود من از طریق آن attribute به NHibernate گفته بودم که آن property حق ندارد null باشد و NHibernate هم فقط داشت این موضوع را به من یادآوری می‌کرد چون من در بعضی جاهای تستم مقدار بعضی propertyها را اصلاً set نمی‌کردم.

این بود علت خطایی که ده روز کامل من را سر کار گذاشته بود و اعتماد به نفسم را از بین برده بود. البته دنیای برنامه‌نویسی و به طور کلی دنیای کامپیوتر پر از این مشکلات است که اولش فکر می‌کنیم چه خطای عجیب و غریب و پیچیده‌ای است و آخرش می‌فهمیم علت آن یک موضوع کاملاً پیش پا افتاده است. من فقط دوست دارم بدانم چطور می‌شود وقوع آن «یک لحظه‌های» طلایی را جلوتر انداخت. نظر شما چیست؟

‫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 – قسمت دوم

‫مصائب پیاده سازی پروتکل ECE

تا زمانی که فقط صحبت از نوشتن کدهای پروتکل و تست آن با دبیرخانه خودتان است همه چیز مثل قند و عسل شیرین است. ولی وقتی که قرار شد این ارتباط با دبیرخانه‌های دیگر تست شود آن وقت می‌فهمید که زهر مار چه مزه‌ای است!

یکی از بزرگترین مشکلات و مصیبت‌ها عدم تست بیشتر دبیرخانه‌های موجود است چون بیشتر دبیرخانه‌های شرکت‌ها صرفا با خودشان تست شده و کار می‌کنند. برای این دسته از دبیرخانه‌ها هیچ وقت پیش نیامده که به دبیرخانه یا اتوماسیونی غیر از محصول شرکت خودشان نامه ECE بفرستند یا بگیرند. و در این حالت این بدبختی من برنامه نویس است که باید هم کدهای خودم را تست کنم و هم محصول شرکت‌های آن طرف خط را که بعضا مدت‌هاست روی کد مربوط به پروتکل کار نکرده‌اند و یا حتی برنامه نویس مربوطه از آن شرکت رفته است. این را هم بیفزایید که بعضی دوستان اصرار دارند از موضع برتر صحبت کرده و هیچ وقت قبول نمی‌کنند که برنامه‌ای که نوشته‌اند ایراد دارد یا این که پروتکل ارتباطی را بد فهمیده‌اند. مثلا یکی از شرکت‌ها که اتفاقا از شرکت‌های قدیمی ایران در امر نرم افزار است رشته زیر را به عنوان تاریخ نامه در XML ارسال می‌کند. در حالی که در پروتکل گفته شده که این رشته باید بر اساس استاندارد ISO 8601 باشد. در تعریف استاندارد ۸۶۰۱ هم گفته شده که این استاندارد بر مبنای تاریخ میلادی است و شامل هیچ رشته‌ای مثل +03:30 هم نمی‌باشد.

 1387-07-22T16:15:35+03:30

عدم رعایت کوچک و بزرگی حروف یا همان Case Sensitivity هم یکی دیگر از مشکلات رایج است. این مشکل در همه جا نمود دارد هم در هدرهای ایمیل و هم در XML تولیدی. مثلا فلان شرکت elementها و attirbuteهای XML را بدون توجه به این که XML به کوچک و بزرگی حروف حساس است تولید می‌کند و بهمان شرکت هم هدر ایمیل را با فرض حساس بودن به کوچک و بزرگ بودن حروف پردازش می‌کند در حالی که هدر ایمیل به حروف کوچک و بزرگ حساس نیست.

در مورد حساسیت هدر ایمیل به کوچکی و بزرگی حروف هم باید گفت که مطابق RFC 2822 و توضیحات اینجا حساس نیست. در اینجا هم گفته شده که نام هدر همیشه غیر حساس به حروف کوچک و بزرگ است ولی مقدار هدر بسته به حالت ممکن است حساس باشد یا نباشد. اگر از literal استفاده شود یعنی از حروف الفبای انگلیسی که محصور در دابل کوتیشن باشند مثل A و Header1 آن وقت حساسیتی وجود ندارد ولی اگر از از مقادیر داده‌ای که شبیه مقادیر اسکی دسیمال و… می‌باشد مثل «%d» قضیه فرق می‌کند.

‫تست پروتکل ارتباطی بین دبیرخانه ما و دبیرخانه شرکت «پ»‏

بالاخره فرایند تست پروتکل ECE بین دبیرخانه شرکت ما و شرکت «پ» شروع شد. این فرایند تا اینجا حدود ۴ هفته طول کشیده و برقراری هر تماس بین ما، شرکت «پ» و کارفرمای مشترک ما هم به طور میانگین ۲ روز طول کشیده!!
متاسفانه همکاری ما با برنامه نویسان شرکت «پ» هم از نوع ناقص آن می‌باشد. چون اولا «هر چی ما نوشته‌ایم درست است و حتما مال شماست که خراب است» و ثانیا «این شما هستید که باید خودتان را با ما تطبیق دهید نه ما، چون ما از شما قدیمی‌تر هستیم و مبنا زور ماست نه استانداردی که پروتکل ECE تعیین کرده!»‏
به قسمت دوم این را هم بیفزایید که آنها نمی‌توانند به ما بگویند از چه روش یا Componentی برای خواندن سورس ایمیل استفاده می‌کنند (اسرار کاری) و ما مجبور هستیم خودمان روش آنها را کشف کرده و منطبق با آن عمل کنیم!
این را هم اضافه کنم که مشکل ما به علت ارتباطات ایمیلی است و نه تولید یا خواندن فایل XML.

پ. ن.: این مطلب در ادامه مطالب وبلاگ سابق http://iranece.blogspot.com در اینجا درج شده است.