Birim test; NUnit, NMock ve TestDriven.Net

Emre: Uzun zamandır görünmüyorsun. Neler yaptın bu arada?

Selçuk: Sana bahsettiğim otomatik oluşturma sürecini yerleştirmeye çalışıyorduk ekip olarak. O zaman da bahsetmiştim, bu işi CruiseControl.NET ile yapıyoruz. Biliyorum detayına girmedim ama gireceğim.

E: Onun dışında birşeyler yok mu?

S: Evet var tabi. 25-27 Mayıs tarihlerinde Microsoft Yazılımcılar Zirvesi oldu, duymuşsundur. Oradaydık. Biz açık kaynak kodlu uygulamalar ile otomatik oluşturma süreci ile uğraşırken (ki dünya da birçok ekip de benzer yöntemlerle çalışıyor) Microsoft Visual Studio Team System ile bu işi bir çatı altında toplamış. En geç 2005 Aralık'da çıkacağı söyleniyor. Ama ben Team System çıkana kadar sana sürecin her parçasından tek tek bahsetmiş olurum. Böylece, Team System'i kullanmaya başlarsan onu çok daha verimli kullanabilirsin. Neyse, henüz beta 2 seviyesinde olan Team system'in detaylarından bir ara yine konuşuruz ya da istersen şu adresten gelişmeleri takip edebilirsin. Şimdi, görüşmemizi sen ayarladığına göre sen sor ben anlatayım?

E: Geçen konuşmamızda sen NAnt'tan sözederken konuşmanın arasında birim test konusu da kısaca geçmişti. Biraz şu birim test olayından bahsetsene.

S: Öncelikle birim test nedir ondan bahsedelim. Birim test, bir yazılım geliştirici tarafından yazılan ve test edilmekte olan kodun işlevselliğinin çok küçük ve belirli bir alanını deneyen kod parçasıdır. Yani, yazdığın kodun doğru çalıştığından emin olmak için yazdığın kod. Test kelimesi, birim test konusuyla ilk karşılaşan yazılımcılarda (ki ne yazık ki hala oldukça çok sayıdalar) yanlış anlamaya neden olabiliyor. Bunu performans, yük, sistem ya da kullanıcı kabul testleri ile karıştırmamalısın. Birim test sadece yazdığın metodların, onlardan yapmalarını beklediğin şeyi yaptıklarından emin olmak için yazdığın kod parçasıdır. Birim test yazmak, bugların azalmasını, yazılım geliştiren ekibin daha basit ve daha okunur kod yazmasını, refactoring işlemlerinin kolaylaşmasını ve geliştirme ekibi içinde oluşturma sürecinin güven içinde yapılmasını sağlar. Birim test yazma konusunda bir çok framework bulunmakta. Bunların birçoğunun atası Kent Beck tarafından geliştirilen XUnit framework'ü. XUnit framework'ü yanılmıyorsam Smalltalk ile yazılmış ancak Kent Beck daha sonra Erich Gamma ile birlikte onu Java'ya taşımış ve JUnit doğmuş. Ardınan birçok dil için benzer frameworkler yazılmış. İşte bu da bizi .NET için varolan toruna, yani NUnit'e getiriyor. NUnit de XUnit'in torunlarından ama 2.2 sürümüne gelmiş olan bu framework oldukça büyük değişimlerden geçerek .NET altyapısına tamamıyla uyum sağlamış.

E: Şimdi sen NUnit anlatmaya başlayacaksın ama, benim aklım henüz bu birim test işine yatmadı ki NUnit dinleyeyim. Yanlış anlamadıysam yazdığım kodu yine yazdığım kodla test ediyorum. Bir dakika, bir dakika... Nasıl ya?

S: Aslında bunu yazılım geliştirme dünyası çok çok uzun yıllardır yapıyor. Hatta nasıl yazılmalı, ne kadar yazılmalı tartışmları biteli bile çok olmuş. Test Driven Development denilen yöntemle daha da ileri gidilerek kod yazmadan önce birim testleri yazman öneriliyor desem bakalım ne diyeceksin. Tamam, tamam. Hepimizin böyle çalışması gerekmiyor. Öncelikle yapılması gereken ortaya çıkacak yazılımın kalitesine karar vermek ve böylece birim test yazma stratejisini belirlemek. Daha sonra birim testlerin gerçek kodun ne kadarını kapsadığında (coverage) yeterli olduğuna karar vermek yeterli olacaktır. Birçok yazılımcı test yazmanın zorluğundan, kodlarının test yazmayı geektirmeyecek kadar basit olduğundan ya da test yazmakla harcayacak zamanı olmadığından dem vurur ve test yazmaktan kaçar. Bu doğal bir tepkidir çünkü farklı bir yöntemle kod yazmaya alışmış biri için birim test yazmaya alışmak çok zorlu bir süreçtir. Yazmaya başladıkça alışacaksın ama sana yol gösterecek birçok kaynak bulmakta zorlanmayacağına da eminim. Benim hoşuma giden bir kaynak Robert C. MArtin'in The Craftsman serisinin ilk birkaç parçasıdır. Neyse şimdi bahsettiğimiz bu birim testleri yazmak için kullandığımız altyapıya yani NUnit'e gelelim.

E: Evet, zaten olayı anlamaya başlıyorum galiba. Ama ben yine de detaylı bir araştırma yaparım. Google ne güne duruyor? Sen devam et.

S: Öncelikle download adresinden NUnit'i indirmelisin. Ben sana bunları anlatırken tutarlı sürümü 2.2.0. Nunit'i kullanmanın iki yöntemi var biri komut satırından (nunit-console.exe) diğeri ise grafik kullanıcı arabirimi (nunit-gui.exe) ile. Bizim gibi testleri otomatik oluşturma aşamasında entegrasyon amaçlı kullanacaksan komut satırında çalışanı kullanacaksın demektir. Grafik arayüzlü olanında, testler bir ağaç yapısında görünüyorlar ve istediğin testi seçip çalıştırıyorsun. Herneyse şimdi test yazma işine geçelim. Öncelikle projende nunit.framework.dll kütüphanesine referans vermelisin. Ardından testleri içeren sınıfa [TestFixture] özelliğini (attribute) ekleyeceksin. Tabi bu sınıf public olmalı ve varsayılan constructor'ı bulunmalı. Sonra bu sınıfın içinde test olarak çalışacak metodlarının başına [Test] özelliğini ekleyeceksin. Bu metodlar kesinlikle public void imzalı olmalı ve parametre almamalılar. TestFixture olan bu sınıfta çalışacak testler için bir ortam yaratmak isteyebilirsin. Bunun için her test metodu çalışmadan önce çalışan bir [SetUp] metodu ile testler çalıştıktan sonra çalışacak bir [TearDown] metodu yazabilirsin. Yazdığın test metodlarından bazılarının Exception atmalarını bekliyor olacaksın, o zaman [ExpectedException] özelliğini kullanmalısın. Yine metodun başına [ExpectedException(BekledigimException)] yazmalısın. Bu özellik içine beklenen exception türünü alıyor dikkat edersen. Bu şu anlama geliyor, bu test çalıştığından ExpectedException'a parametre olarak verdiğin hata atılıyorsa test başarılı sonuçlandı, yok öyle bir hata atılmadı ise test başarısız oldu. Ayrıca testlerini [Category("GuzelKategori")] yöntemi ile kategorilere de dahil edebilirsin.

E: Böyle birşeye neden ihtiyacım olur ki?

S: Böylece bir TestFixture'ı çalıştırırken içinden bazılarını çalıştırmamayı seçebilirisin. Bunu komut satırında /include ya da /exclude argümanları, grafik arabirimde ise Categories sekmesi aracılığıyla yapabilirsin. Bir Test'in başlangıcında [Ignore("Bu testi atla,cunku su bu")] kullanarak o Test'in çalışmamasını da sağlamak mümkün. Ignore'a verdiğin string sonuçlar raporlanırken bu Test'in neden atlandığını belirtirken kullanılır. Bazen geçici olarak bir veya birkaç test dışındakileri çalıştırmak isteyebilrisin, bu tip durumlarda o kısımları yorum satırına çevirmektense bu yöntemi kullanmak daha makul görünüyor.

E: ExpectedException durumu hariç bir testin ne zaman başarılı ne zaman başarısız olduğu durumundan bahsetmeyecek misin?

S: Tabi ya, en elzem kısmı atladım değil mi? İşte bunu Assertion'lar yani teyit komutları aracılığıyla yapıyorsun. Teyit komutları karşılaştırmalar, koşul testleri ve emirler biçiminde üç grupta düşünülebilir. Karşılaştırmalar AreEqual ve AreSame, koşul testleri IsFalse, IsNull, IsNotNull ve IsTrue, emirler ise Fail ve Ignore'dur. Bunları Assert.IsNull(object birObje) gibi kullanıyorsun. Diğer kullanımları için intellisense ve dökümanlardan faydalanabilirsin.

E: Diğerlerini anladım da AreEqual ile AreSame arasındaki fark ne?

S: AreSame(object birObje, object digerObje) birObje ile digerObje'nin aynı obje olup olmadığını kontrol ediyor. Yani her iki argümanın da aynı objeye referans olup olmadığını. AreEqual(object beklenen, object gelen) ise beklenen ile gelen objelerin eşitliklerini kontrol ediyor. Örneğin AreEqual(7, 7.0) true döner. İşte hepsi bu kadar. İstersen küçük bir örnek yazayım anlattıklarımaışık tutsun. Şöyle garip bir sınıf olduğunu düşün...

public class Sicaklik
{
private Decimal _selsiyus=27;
private Decimal _fahrenayt=80;

public Decimal Selsiyus
{
get{return _selsiyus;}
set{_selsiyus=value;}
}

public Decimal Fahrenayt
{
get{return _fahrenayt;}
set{_fahrenayt=value;}
}

public void SelsiyustanFahrenaytaDonustur()
{
_fahrenayt = ((_selsiyus*9)/5)+32;
}
public void FahrenayttanSelsiyusaDonustur()
{
_selsiyus = ((_fahrenayt-32)*5)/9;
}
}

Şimdi de testlerine bakalım.

[TestFixture]
public class TestEdelim
{
public TestEdelim(){}
private Sicaklik sic;

[SetUp]
public void Baslangic()
{
sic = new Sicaklik();
}

[Test]
[Category("KategoriOrnegi")]
public void SelsiyustanFahrenayta()
{
sic.Selsiyus = 37;
sic.SelsiyustanFahrenaytaDonustur();
Assert.AreEqual(98.6, sic.Fahrenayt,"Fahrenayta dönüştürmede hata!");
}

[Test]
[Category("BirBaskaKategoriOrnegi")]
public void FahrenayttanSelsiyusa()
{
sic.Fahrenayt = 98.6M;
sic.FahrenayttanSelsiyusaDonustur();
Assert.AreEqual(37, sic.Selsiyus,"Selsiyusa dönüştürmede hata!");
}
}

Yo yo, dur bir dakika bir şeyi atlıyorum. Bazen test yazmak daha da zor olabilir. Örneğin bazen test edeceğin sınıf birden fazla sayıda alt sistemle etkileşimde olabilir. Birim olarak test edebilmek için sınıfı soyutlaman gerekir. O zaman gereken sınıf bağımlılıklarını yaratmak için taklit nesneler kullanman gerekir. Bu taklit nesneler arayüzleri taklit etmek için kullanılırlar. Yaratılan bu nesnelere İngilizce'de sahte, taklit anlamına gelen Mock deniyor. Bu taklit nesneleri yaratmak için hazır araçlar mevcut. Bunlar, bağımlı olunan sınıfların taklit implemantasyonlarını oluşturan statik araçlar ve taklit implemantasyonları runtime'da oluşturan dinamik araçlar olarak iki grupta düşünülebilir. Dinamik olanları kullanmak daha kolay olduğundan ve fazladan sınıflar oluşturmadıklarından daha çok tercih ediliyorlar. .NET için NMock ve DotNetMock'dan bahsedilebilir. Biz NMock'u kullanıyoruz.

E: Bir dakika ya. Anlayamadım.

S: Bir örnek versem daha netleşecek sanırım. Diyelim ki şöyle bir interface'in var:


public interface IKopek
{
string Ad { get; }
}

Ve diyelim ki Yakala isimli bir metodu olan Komut isimli bir sınıfın olsun:

public class Komut
{
IKopek kopek;

public Komut(IKopek kopek)
{
this.kopek = kopek;
}

public String Yakala()
{
return "Yakala " + kopek.Ad;
}
}

Görüdüğün gibi Komut satırı IKopek'e bağımlı. Şimdi öncelikle taklit etmek istediğimiz interface'in ya da sınıfın tipini vererek taklit nesneyi başlatacağız. Daha sonra nasıl davranacağını belirtip taklitin çalışan bir kopyasını yaratacağız. Son olarak da taklit nesnenin Verify() metodunu çağıracağız.

IMock mockKopek = new DynamicMock(typeof(IKopek)); // taklitini istediğimiz interface budur diyoruz
mockKopek.ExpectAndReturn("Ad","Çomar"); // davranışını belirtmeden işimize yaramaz
IKopek kopek = (IKopek)mockKopek.MockInstance; // takliti başlatıyoruz
mockKopek .Verify();

Burda bir örneğini gördüğün Expect metodlarından NMock bünyesinde yeteri kadar var. Dolayısı ile taklit nesnenin davranışını detayıyla tanımlayabiliyorsun. Expect(string metodAdi, object[] args), ExpectAndReturn(string metodAdi, object donusDeger, object[] args), ExpectAndThrow(string metodAdi, Exception hata, object[] args) ve ExpectNoCall(string metodAdi).
Şimdi de bunu bir test içinde nasıl kullandığına bakalım.

[TestFixture]
public class KomutTesti
{
[Test]
public void Testtest()
{
IMock mockKopek = new DynamicMock(typeof(IKopek));
mockKopek.ExpectAndReturn("Ad","Çomar");

Komut komut = new Komut((IKopek) mockKopek.MockInstance);
Assert.AreEqual("Yakala Çomar", komut.Yakala());

mockKopek.Verify();
}
}


E: Sanırım NMock olayını anladım. Saol ya. İyi oluyor böyle örnekli felan. Ben NUnit'e dönüp birşey soracam. Bu NUnit'i Visual Studio.NET içinden çalıştırmak mümkün mü?

S: Bunu soracağını tahmin ediyordum ama beklediğimden geç sordun. Cevabım evet. Visual Studio ile kullanmak için TestDriven.Net isimli Visual Studio.NET eklentisini kurabilirsin. Bu eklentiyi kurduğunda Visual Studio.NET içinde Solution Explorer'da sınıflara sağ tıkladığında açılan menüde Debugger ve NUnit GUI komutlarını içeren Test with... alt menüsü ekleniyor. Bu komutlar aracılığıyla NUnit'i çağırabiliyorsun.

E: Sağol abi yaa. Bunca bilgiden sonra bana gidip bunları iyice kurcalamak kalıyor. Teşekkürler. Ama bir daha arayı bu kadar uzatmayalım. Kendine iyi bak...

S: Kal sağlıcakla...


spinodal tarafından 23.05.2005 tarihinde yazılmıştır.

Labels: , ,

2 Comments:

Anonymous Anonymous said...

Son derece faydalı bir makale. TestDriven.Net bedava bir eklenti mi?

Teşekkürler...

4/07/2008 3:49 AM  
Anonymous Anonymous said...

Teşekkürler, gayet faydalı bir yazı olmuş.

4/07/2008 11:32 PM  

Post a Comment

Subscribe to Post Comments [Atom]

<< Home