استفاده از mocking frameworks :
تعدادي از چارچوبهاي تقليد نوشته شده براي دات نت فريم ورك مطابق ليست زير بوده و هدف از آنها ايجاد سادهتر اشياء تقليد براي ما ميباشد:
Nmock : http://www.nmock.org
Moq : http://code.google.com/p/moq
Rhino Mocks : http://ayende.com/projects/rhino-mocks.aspx
TypeMock : http://www.typemock.com
EasyMock.Net : http://sourceforge.net/projects/easymocknet
در اين بين Rhino Mocks كه توسط يكي از اعضاي اصلي تيم NHibernate به وجود آمده است، در مجامع مرتبط بيشتر مورد توجه است. براي آشنايي بيشتر با آن ميتوان به اين ويديوي رايگان آموزشي در مورد آن مراجعه نمود (حدود يك ساعت است).
خلاصهاي در مورد نحوهي استفاده از Rhino Mocks :
پس از دريافت كتابخانه سورس باز Rhino Mocks ، ارجاعي را به اسمبلي Rhino.Mocks.dll آن، در پروژه آزمون واحد خود اضافه نمائيد.
يك Rhino mock test با ايجاد شيءايي از MockRepository شروع ميشود و كلا از سه قسمت تشكيل ميگردد:
الف) ايجاد شيء Mock يا Arrange . هدف از ايجاد شيء mock ، جايگزين كردن و يا تقليد يك شيء واقعي جهت مباحثي مانند ايزوله سازي آزمايشات، بالابردن سرعت آنها و متكي به خود كردن اين آزمايشات ميباشد. همچنين در اين حالت نتايج false positive نيز كاهش مييابند. منظور از نتايج false positive اين است كه آزمايش بايد با موفقيت به پايان برسد اما اينگونه نشده و علت آن بررسي سيستمي ديگر در خارج از مرزهاي سيستم فعلي است و مشكل از جاي ديگري نشات گرفته كه اساسا هدف از تست ما بررسي عملكرد آن سيستم نبوده است. كلا در اين موارد از mocking objects استفاده ميشود:
- دسترسي به شيء مورد نظر كند است مانند دسترسي به ديتابيس يا محاسبات بسيار طولاني
- شيء مورد نظر از call back استفاده ميكند
- شيء مورد آزمايش بايد به منابع خارجي دسترسي پيدا كند كه اكنون مهيا نيستند. براي مثال دسترسي به شبكه.
- شيءايي كه ميخواهيم آنرا تست كنيم يا براي آن آزمايشات واحد تهيه نمائيم، هنوز كاملا توسعه نيافته و نيمه كاره است.
ب) تعريف رفتارهاي مورد نظر يا Act
ج) بررسي رفتارهاي تعريف شده يا Assert
مثال:
متد ساده زير را در نظر بگيريد:
public class ImageManagement
{
public string GetImageForTimeOfDay()
{
int currentHour = DateTime.Now.Hour;
return currentHour > 6 && currentHour < 21 ? "sun.jpg" : "moon.jpg";
}
}
using System;
using NUnit.Framework;
[TestFixture]
public class CMyTest
{
[Test]
public void DaytimeTest()
{
int currentHour = DateTime.Now.Hour;
if (currentHour > 6 && currentHour < 21)
{
const string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("تنها در طول روز قابل بررسي است");
}
}
[Test]
public void NighttimeTest()
{
int currentHour = DateTime.Now.Hour;
if (currentHour < 6 || currentHour > 21)
{
const string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("تنها در طول شب قابل بررسي است");
}
}
}
براي اينكار بايد DateTime.Now.Hour را تقليد نموده و اينترفيسي را بر اساس آن طراحي نمائيم. سپس Rhino Mocks كار پياده سازي اين اينترفيس را انجام خواهد داد:
using NUnit.Framework;
using Rhino.Mocks;
namespace testWinForms87
{
public interface IDateTime
{
int GetHour();
}
public class ImageManagement
{
public string GetImageForTimeOfDay(IDateTime time)
{
int currentHour = time.GetHour();
return currentHour > 6 && currentHour < 21 ? "sun.jpg" : "moon.jpg";
}
}
[TestFixture]
public class CMocking
{
[Test]
public void DaytimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);
}
using (mocks.Playback())
{
const string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}
[Test]
public void NighttimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(1);
}
using (mocks.Playback())
{
const string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}
}
}
MockRepository mocks = new MockRepository();
سپس اينترفيسي بايد به آن پاس شود تا انتظارات سيستم را بتوان در آن بر پا نمود:
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);
}
به اين صورت آزمايش ما بر اساس وضعيت مشخصي از سيستم صورت ميگيرد و وابسته به ساعت جاري سيستم نخواهد بود.
همانطور كه ملاحظه ميكنيد، روش Test Driven Development بر روي نحوهي برنامه نويسي ما و ايجاد كلاسها و اينترفيسهاي اوليه نيز تاثير زيادي خواهد گذاشت. استفاده از اينترفيسها يكي از اصول پايهاي برنامه نويسي شيءگرا است و در اينجا مقيد به ايجاد آنها خواهيم شد.
پس از آنكه در قسمت mocks.Record ، انتظارات خود را ثبت كرديم، اكنون نوبت به وضعيت Playback ميرسد:
using (mocks.Playback())
{
string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
با توجه به اينكه پس از تغيير طراحي متد GetImageForTimeOfDay ، اين متد اكنون از شيء IDateTime به عنوان ورودي استفاده ميكند، ميتوان پياده سازي آن اينترفيس را در آزمايشات واحد تقليد نمود و يا جايي كه قرار است در برنامه استفاده شود، ميتواند پياده سازي واقعي خود را داشته باشد و ديگر آزمايشات ما وابسته به آن نخواهد بود:
public class DateTimeController : IDateTime
{
public int GetHour()
{
return DateTime.Now.Hour;
}
}