Evvel zaman icinde kalbur zaman icinde develer tellal iken pireler berber iken ben ninemin besiginde tingir mingir sallanirken keloglan bilgisayarin basinda kafayi cizmek uzereymis. Hemde bu cizme olayi o kadar ilerlemiski keloglanin kafasinda sac kalmamis, karakacan acliktan zayiflamis, aykiz bekleye bekleye otobus duragi olmus. Tum bu olanlarin nedeni ise keloglanin yazilim gelistirirken uygulamis oldugu birim testlerinin cok cok zaman almasiymis.

Keloglan ‘in bazi nesneleri kafdaginin arkasina internet uzerinden baglanip anka kusunun haftanin hangi gunleri bos oldugunu soruyor bazi nesneleri sihirli guvercinin nerede oldugunu ogreniyor bazi nesneleri de uzaktaki web servislerine baglanip kendi hesabindan anasinin hesabina havale yapiyormus.

Bu islemlerde kendi ic ortamindan bagimsi,z dis ortamlarla alakali oldugu icin dogal olarak birim testlerinin calismasi uzun suruyormus.

Madem durum bu gelin hep birlikte el atalim ve keloglani, karakacani ve aykizi bu iskenceden kurtaralim :)
Teste dayali yazilim gelistirirken onumuze cikan problemlerden bir tanesi de disa bagimli olan nesnelerdir. Bu nesneler dis dunyayla iletisim kurarak gerekli parametreleri dis dunyadaki servise aktarirlar ve gelen cevaba gore islem yapmaya devam ederler. Ornek vermek gerekirse bir kisinin tc kimlik numarasini bulmak icin tckimlik.nvi.gov.tr adresindeki web servislerini bu olaya ornek olarak gosterebiliriz. Bu durumun bize getirdigi dezavantaj yavasliktir. Eğer birim testlerimizin icerisinde bu servisi kullanan bir nesnemiz varsa hem bizimle alakasi olmayan bir servisin test edilmesi soz konusu hem de birim testlerimizin calisma suresini cok cok uzatmasi soz konusu. Bu dezavantajlari ortadan kaldirarak birim testlerinin calisma suresini oldukca kisaltabiliriz.

sorunu cozmek icin asmamiz gereken iki problem var.
1- dis ortamlara olan cagrilari test kodlarimizin arasindan kaldirmaliyiz
2- dis ortamlara olan cagrilari kaldirirken oyle bir ayar cekmemiz lazim ki diger kodlar (testler) sanki dis ortama baglanip veri almaya devam ediyorlarmis gibi hareket etmeliler.

bu iki secenegi birden yerine getirebilmemiz icin bize yardimci olacak elemanlara biz taklit nesneler (mock objects) diyoruz :)
http://www.mockobjects.com/
http://en.wikipedia.org/wiki/Mock_object
http://sourceforge.net/projects/pascalmock/

Pascal mock adli kutuphaneyi indirdim ve icindeki kendi ornegine baktim birazcik. taklik sinifi olusturup sanal parametreleri ve metodun geriye hangi degeri dondurmesi gerektigini belirtip testi calistiriyorsunuz ve dis ortama bagimli olan metodunuz verdiginiz parametrelere uygun olarak sanki dis ortamdan verileri almista gelmis ayagina yatiyor. ba ba ba ! bizi kandiracak :)
Pascal Mock ile ugrasirken birseyin eksik oldugunu fark ettim veya ben beceremedim. bu yuzden daha degisik bir cozum yontemi uygulamak zorunda kaldim. karsima cikan problem suydu.

A sinifinin private metodlarindan birisinde disa bagimli olan B sinifini kullandigim zaman pascal mock ile B sinifi icin taklit nesne olusturamadim. biraz daha detaya girersek durum su sekilde izah edilebilir.

THasta adinda bir sinifim var ve bu sinifimin Ad, Soyad, TCKimlik no gibi alanlari ve bir tane de TedaviEt diye public metodu var. Gerekli bilgileri girip ardindan TedaviEt metodunu calistirdiginiz zaman nesnemiz girilmis olan TCKimlik nosunu kullanarak private olan GecerliProvizyonuVarmi adli metodu calistirarak internet uzerinden kisinin saglik yardimi almaya mustehak olup olmadigini kontrol ediyor oncelikle. eger gecerli bir provizyon bulunamazsa tedavi etmiyor aksi durumda tedavi islemleri icin gerekli olan kodlari sirayla islemeye devam ediyor.

GecerliProvizyonuVarmi adli metod ise kendi icerisinde TProvizyon adinda bir sinifi kullaniyor. Once sinifi create ediyor ardindan TCKimliknumarasini TProvizyon sinifinin GetProvizyon adli metoduna parametre olarak geciyor ve donen sonuca gore islemlere devam ediyor.

Resize of 1.JPG

THasta = class
private
FAd: String;
FSoayad: String;
FTCKimlikNo: String;
procedure SetAd(const Value: String);
procedure SetSoayad(const Value: String);
procedure SetTCKimlikNo(const Value: String);
Function GecerliProvizyonuVarmi():Boolean;
{ Private declarations }
public
function TedaviEt():Boolean;
property Ad:String read FAd write SetAd;
property Soayad:String read FSoayad write SetSoayad;
property TCKimlikNo:String read FTCKimlikNo write SetTCKimlikNo;
{ Public declarations }
end;

function THasta.GecerliProvizyonuVarmi: Boolean;
var
Provizyon:TProvizyon;
begin
Provizyon :=TProvizyon.Create;
try
Result := Provizyon.GetProvizyon(FTCKimlikNo);
finally
FreeAndNil(Provizyon);
end;
end;

THastanin diger metodlari siradan metodlar oldugu icin burda yazmanin bi manasi yok gibi.

TProvizyon = class(TInterfacedObject,IProvizyon)
private
{ Private declarations }
public
function GetProvizyon(TCKimlik: String): Boolean;
{ Public declarations }
end;

implementation

{ TProvizyon }

function TProvizyon.GetProvizyon(TCKimlik: String): Boolean;
begin

{ bu metod internette ki bir web servisinden verilen kimlik
numarasinin gecerli bir provizyona sahip olup olmadigini test eder.
sorgulama islemi disarida ki bir sistemden yapildigindan sorgulama
suresi 4-10 saniye arasinda surmektedir.
}
Sleep(5000);
Result :=True;
end;

provizyonun kodu da basitti. burda kafa karistiran tek sey Provizyonun neden bir interfaceden turedigidir. Taklit bir nesne daha yazmamiz gerektiginden bu taklitligi saglamlastirmamiz gerekiyordu. bunu da her iki sinifi da ayni interfacden alarak saglamaya calisacagiz.

IProvizyon = Interface
['{B9D1F50C-0383-423D-AD6A-081CBD725048}']
function GetProvizyon(TCKimlik:String): Boolean;
end;

bu da test kodumuz….

THastaTests = class(TTestCase)
private
fHasta:THasta;
protected
procedure SetUp; override;
procedure TearDown; override;

published

// Test methods
procedure TestTedaviEt;

end;

implementation

{ THastaTests }

procedure THastaTests.SetUp;
begin
inherited;
fHasta :=THasta.Create;
end;

procedure THastaTests.TearDown;
begin
fHasta.Free;
inherited;
end;

procedure THastaTests.TestTedaviEt;
begin
fhasta.Ad :='a';
fHasta.Soayad :='b';
fHasta.TCKimlikNo :='1234567890777';
fHasta.TedaviEt;
end;

TProvizyonun test edilecek bir tarafi yok gibi. zaten disa bagimli oldugu icin veri gonderip veri aliyor.

simdi ilk once 1. problemi cozelim.

1- dis ortamlara olan cagrilari test kodlarimizin arasindan kaldirmaliyiz
bunu yapmak icin TProvizyon sinifindan dis ortama yapilan cagriyi kaldirmamiz gerekiyor. Bu da yeni bir sinif manasina gelebilecegi gibi baska cozumlerde mevcut. bu kisim sizin nasil kod yazdiginizla ilgili birazcik. Nerde nasil yapacaginiza siz karar verin. benim yontemi de merak ederseniz okumaya devam edin :)
TProvizyonMock = class(TInterfacedObject,IProvizyon)
private
{ Private declarations }
public
function GetProvizyon(TCKimlik: String): Boolean;
{ Public declarations }
end;

{ TProvizyonMock }

function TProvizyonMock.GetProvizyon(TCKimlik: String): Boolean;
begin
Result := true;
end;

seklinde bir taklit sinif olusturuyorum ve GetProvizyon metodundaki dis ortamdan yapilan istegi iptal edip geriye sadece metoda uygun bir deger donduren kod yaziyorum.

bu sayede 1. problemi asmis oldum.

simdi sira da ikinci problem var.

2- dis ortamlara olan cagrilari kaldirirken oyle bir ayar cekmemiz lazim ki diger kodlar (testler) sanki dis ortama baglanip veri almaya devam ediyorlarmis gibi hareket etmeliler.

bu problemi cozmenin yollarindan biris derleyici direktifleri kullanmak. hatirlarsaniz asil kodlarimizin bulundugu bir projemiz ve bir de test kodlarimizi calistiran bir projemiz mevcut. Derleyici hangi proje aktifse ona uygun olan nesneyi olusturmakla sorumlu. bu sayede normal programi derlerken dis ortama cagrida bulunan nesnemiz is gorurken test projemizde taklit nesnemiz is goreceginden dolayi testlerimiz daha kisa bir sure icerisinde tamamlanmis olacak.

Test projemizi acip Project –> Options seceneklerinden Directories/Conditionals bolumunde ki Conditionals Defines bolumune DUNIT ekleyelim. bu sayede projemiz icin global bir deger tanimlamis olduk. projede yer alan her unitte {$IFDEF DUNIT} komutunu saglikli bir sekilde kullanabiliriz. Eger direkt kaynak kodun icerisinde {$DEFINE DUNIT} yazarsak yaptigimiz bu tanimlama sadece gecerli unitte aktif olacagindan Directories/Conditionals bolumu bizim icin daha saglikli bir sonuc verecektir.

function THasta.GecerliProvizyonuVarmi: Boolean;
var
Provizyon:IProvizyon;
begin
{$IFDEF DUNIT}
Provizyon :=TProvizyonMock.Create as IProvizyon;
{$ELSE}
Provizyon :=TProvizyon.Create as IProvizyon;
{$ENDIF}
try
Result := Provizyon.GetProvizyon(FTCKimlikNo);
finally
Provizyon :=nil;
end;
end;

Bu sekilde taklit nesnemizin bir varmis bir yokmus olmasini saglamis olduk :) gokten uc elma dustu. biri benim basima biri CodeGear in basina biri de senin basina :)
dipnot: ifdef sartini GetProvizyon metodunun icerisine de yerlestirebiliriz. o yontem daha az kod yazmamizi gerektirebilir ama sirf isin icine bu interfaceleri de katmak icin ben bu sekilde yaptim :)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>