Bundan önceki yazılarımda bazı yerlerde, yazılan programların testlerinden çok üstün         körü de olsa bahsetmiştim. Şimdi ise bu ihtiyacın neden ortaya çıktığından ve nasıl         bir yöntemle yapılabileceğinden bahsetmeye çalışacağım.
Her yazılımcının eninde sonunda yazdığı kodları bir şekilde test ediyor olması gerekir.         Ancak bu şekilde yaptığı işin gerçekten çalışıp çalışmadığından haberdar olabilir. Daha         sonra yazdığı kodlar bir ürüne dönüştüğünde de bunlara hızlı cevap verebiliyor olması         o yazılımcı için ciddi bir önem taşımaktadır. Hatta kimi zamanlarda, yazılımcının         önceden yaptığı bazı şeylerin yöntemlerini değiştirip bunların testlerini baştan         yapması gerekir. İşte bu gibi durumlarda, yazılımcının herşeyi eliyle test ediyor         olması hem bir zaman kaybıdır hem de hataya açık bir yöntemdir. Tabi bunun yanında         hem eski kodların çalışabilirliğini kontrol etmek için hem de ihtiyaç kadar geliştirme         yapabilmek için bazı yöntemler sunulmuştur. Bu yazıda onlara değineceğim.
Test Driven Development Nedir?
Test Driven Development (TDD), isminden de anlaşılacağı gibi test bazlı bir yazılım         geliştirme yöntemidir. Bu yöntem, basit ve kısa adımlarla, önceden tasarlanmış senaryoları         hızlı bir şekilde koda dökmeyi hedeflemektedir. Bunun yanında bu yöntemin pek çok         avantajı da geliştirme sırasında fark edilebilir, ancak bunlara yazının ilerleyen         kısımlarında değinmek istiyorum.
TDD, yazılımcıları basit ve küçük parçalardan oluşan tasarımlar yapmaya zorlamaktadır.         Çünkü TDD'nin temel prensibi kodlama sürecini küçük parçalara bölmek üzerine         kuruludur. Kent Beck bu yöntemi anlattığı "Test-Driven Development: By Example"         isimli kitabında iki basit konsept üzerinde durmaktadır,
    -  Elinizde başarısız bir test senaryosu olmadan asla tek satır kod bile yazmayın,
 
    -  Kod tekrarı yapmayın.
 
Bu iki basit madde ileride William Wake'in "Extreme Proramming Explored"         adlı kitabında aşağıdaki algoritmaya dönüşmüştür, 
    - Test kodunu yaz,
 
    - Test kodunu derle, (bu aşamada kodun derlenemiyor olması gerekmektedir. Çünkü test             kodunda yazdığımız hiç bir şeyi aslında henüz tanımlamadık)
 
    - Test kodunun derlenmesine yetecek kadar kod yazıp kodu derle,
 
    - Testleri çalıştır ve testlerin başarısız olduğunu gör,
 
    - Sadece testlerin başarılı olmasını sağlayacak kadar tanım yap,
 
    - Testleri tekrar çalıştır ve başarılı olduğundan emin ol,
 
    - Kodun açıklayıcı olması ve tekrarlanmaması için gerekli düzenlemeleri yap,
 
    - Bir sonraki adım için başa dön.
 
Buradaki adımlardan da görüldüğü üzere tüm tasarım, küçük küçük parçalara bölündüğü         gibi her bir özellik de aslında bir o kadar küçük parçalardan oluşmaktadır. Buradaki         asıl amaç, yazılımcının sistemli ve düzenli bir şekilde sadece yapmayı hedeflediği         küçük parçaya hakim olması ve gerekli testleri bir kere geçtikten sonra da buraya         bir daha dönmemesidir. Fakat bu adımlarda bahsedilen bir kaç küçük ayrıntıya da         değinmek istiyorum, örneğin "kodun derlenmesine yetecek kadar kod yazmak"         tam olarak ne anlama gelmektedir? Ya da testin başarılı olduğunu nasıl anlarım?         Aslında bu yazıda buna da değinmek istiyordum ancak Burak Selim Şenyurt'un bu         konuda hazırladığı görüntülü         anlatımı izlediğimde burada ne dersem cılız kalacağından daha çok vereceğim         örnek üzerine gitmemin daha iyi olacağına karar verdim. Bu yazıda bu teknik ile,         analizi tamamlanmış bir liste nesnesini yapmaya çalışacağım.
Örnek Proje; Generic List
İlk etapta yapmamız gereken şey, hedefimizi belirlemektedir. Bu yazıda, hepimizin         pek çok defa kullandığı .NET kütüphanesindeki generic listenin bir benzerini tasarlamaya         çalışacağım. Generic listemiz aşağıdaki özelliklere sahip olmalıdır,
    - Liste, tanımlanırken verilen bir tipin koleksiyonu olmalıdır,
 
    - Liste, yaratıldıktan sonra içi boş olmalıdır,
 
    - Listeye yeni bir eleman ekledikten sonra listenin eleman sayısı da bir artmalıdır,
 
    - Listeden bir elemanı sildikten sonra listenin eleman sayısı da bir azalmalıdır,
 
    - Listeden eleman silme işlemi, indeks numarasını vererek ya da elemanın kendisini             vererek mümkün olmalıdır.
 
    - Listenin içerisinden bir elemanın indeksini almak istediğimizde sıfır bazlı bir             indeks olarak değer dönmeli, eğer bu eleman listenin bir elemanı değilse o zaman             -1 dönmelidir.
 
Buraya kadar basit bir listeyi tanımladık ve aslında işin önemli bir kısmını bitirmiş         olduk. Artık yapmamız gereken şey elimizdeki senaryoyu test koduna dökmek. Buna         başlamak için Visual Studio ile yeni bir solution yaratıp, ilgili solutiona yeni         bir test projesi eklemekle işe başlamamız gerekiyor. Bununla ilgili olarak yukarıda         bahsettiğim görüntülü dersi inceleyebilirsiniz. Bundan sonraki ilk işimiz birinci         adım için test kodunu yazmak olacaktır,
[TestMethod] 
public void InitializationTest() 
{ 
    GenericList<string> list = new GenericList<string>(); 
    Assert.AreEqual(list.Count, 0, "Liste ilk yaratıldığında listenin eleman sayısı sıfır olmalıdır."); 
}
Aynı şekilde sanki yukarıdaki listedeki maddeleri yazar gibi olmasını istediğimiz         kod parçasını yazdık. Şu aşamada elimizde ne GenericList sınıfı ne de o sınıfa ait         Count diye bir özellik mevcut. Şu anda sadece GenericList adında bir sınıf olması         gerektiğini, bu sınıfın parametresiz yapıcı bir method ile örneklenmesi gerektiğini,         bu nesnenin Count diye bir özelliğinin olmasını gerektiğini ve bu nesne yaratılırken         bu özelliğin de değerinin sıfır olması gerektiğini yazdık. Bunu yaptıktan sonra         projemizi derlemek istediğimizde C# derleyicisi bize derleme anında GenericList<string>         gibi bir sınıfın olmadığından bahsedecektir ve tabi ki derlenmeyecektir. Bu problemi         çözmek için hemen ilgili sınıfı yaratmaya koyulalım. Bu sınıfı yaratırken de Count         diye bir özellik ekleyelim.
public class GenericList<T> 
{ 
    private int m_Count = 0; 
    public int Count 
    { 
        get{return m_Count;} 
    }
} 
Evet artık kodumuzu derleyip testi çalıştırdığımızda testi geçtiğimizi görebiliriz.         Programımız ilk test senaryomuzu doğruladığına göre bir sonraki adım için başa dönüp,         sonraki test senaryosunu yazmamız gerekiyor,
[TestMethod]
public void AddItemTest()
{
    GenericList<string> list = new GenericList<string>();
    list.AddItem("Yeni bir liste elemanı");
    Assert.AreEqual<int>(list.Count, 1, "Listenin eleman sayısı yeni bir eleman eklendiğinde bir artmalıdır.");
}
Yukarıda yine AddItem(string) imzasına sahip bir method varmış gibi düşünüp bu methodu         kullandık. Burada da dikkat ederseniz yaptığımız şey, listeye bir eleman ekleyip         listenin Count özelliğinin artıp artmadığını kontrol etmek oldu. Yine kodumuzu derlediğimizde         hata alacağız. Hatayı gidermek için AddItem(string) imzalı bir methodu da aşağıdaki         gibi ekleyebiliriz.
public class GenericList<T>
{
    private int m_Count = 0;
    public int Count
    {
        get{return m_Count;}
    }
    public void AddItem(T item)
    {
    }
}
Yukarıda görüldüğü gibi kodu derlenebilir hale getirdik ve içeriğini henüz doldurmadık.         Burada yapmamız gereken şey testi yeniden çalıştırıp, testi geçemediğini görüp,         testi geçmesini sağlayacak kodu yazmak olacaktır. Burada birazdan yapacağım şey         aslında basit bir şekilde nesnenin içerisinde bir dizi tutmak ve dizinin elemanlarını         eklenen her bir yeni eleman ile birlikte doldurmak olacaktır. Bunun çok fazla detayına         girmeden aşağıdaki gibi bir ekleme yapıyorum,
public class GenericList<T>
{
    private T[] m_InnerItems = new T[0];
    private int m_Count = 0;    
    public int Count
    {
        get{return m_Count;}
    }
    public void AddItem(T item)
    {
        T[] newItemsArray = new T[m_Count + 1]; 
        Array.Copy(m_innerItems, newItemsArray, m_innerItems.Length);
        newItemsArray[m_Count] = item;
        this.m_InnerItems = newInnerList;
        m_Count++;
    }    
}
Yukarıda sınıfın içerisinde T tipinden oluşan bir dizi yarattım ve eklenen her bir         elemanla birlikte bu diziyi genişletmek için yeni bir dizi yaratıp eski diziyi bunun         üzerine kopyaladım. Böylece yeni eklenen elemanı da dizinin en sonuna ekleyip işlemi         tamamladım. Bu işlemi de gerçekleştirdikten sonra testimi çalıştırdığımda testin         başarıyla tamamlandığını gördüm. Bundan sonra yapmam gereken şey, özellikler listemizdeki         bir sonraki özelliğe gidip onu da bu aşamalardan geçirmek olacaktır. Ancak ben bunun         tamamını burada anlatarak yazıyı sıkıcı bir hale getirmek istemiyorum. Ancak bu         örneğin tamamını   buraya tıklayarak indirebilirsiniz.
Eğer dikkat ederseniz yukarıda yazdığımız tüm methodlar ya da parçaları sadece          ihtiyacımızı karşılamaya yönelik gelişti. Yani ihtiyacımız olmayan hiç bir şey          ile vakit harcamadık, böylece yapmamız gereken işe tam bir konsantrasyon          sağlayabildik. Ayrıca ileride Count özelliğini etkileyen bir değişiklik          yaptığımızda tüm testleri çalıştırarak önceki yerlerde bir sorun yaşayıp          yaşamadığımızı da o anda öğrenebiliyor olacağız. Bunlarda kullandığımız Assert          sınfı üzerinde tanımlı olan neredeyse tüm methodlara aynı zamanda bir mesaj da          ekliyebiliyor olmamız bize ileride hangi testin ne amaçla yazıldığı konusunda da          fikir vermektedir.
Unit Testing bize, yukarıda da bahsettiğim gibi, daha stabil bir çalışma ortamı          sağlamaktadır. Bu yüzden mümkün oldukça projelerimizde bunu kullanıyor olmamız          projelerimizde hata çıkmasını önlemektedir. Ancak elbette her uygulama          yukarıdaki örnek kadar basit olmayabilir. Örneğin kullanıcı arayüzü olan bir          projede unit testin nasıl sağlanabilir? Bunun için düşünülmüş ve çözüm olarak          sunulmuş tasarım kalıplarından biri olan MVP yi önceki yazımda anlatmıştım. Yani          yapmak istediklerimizi ne kadar çok parçalarsak, buna bağlı olarak o kadar test          yapmaya uygun bir hale gelmektedir.
Bir sonraki yazımda bu konuya biraz daha detaylı yaklaşacağım. Aynı zamanda          biraz daha ileri test methodlarını inceleyip, bu metholarla birlikte bir MVP          tasarım kalıbını TDD ile nasıl oluşturabiliriz ondan bahsediyor olacağım. Bir          sonraki yazıda görüşmek üzere.