TDD (Test-Driven Development) Yapmak Gerçekten Zor Mu?
TDD (Test-Driven Development, Test Güdümlü Geliştirme)‘nin en karışık gelen adımı büyük ihtimalle daha kodu yazmadan testleri yazıyor olmamızdır. Geleneksel geliştirme yöntemlerini tercih edenler için aslında gerçekten anlaşılması zor bir kavram. Gündelik hayatta da bazı şeyleri denemek istediğimiz zaman, denemek istediğimiz şeye somut olarak sahip olduğumuzdan, kodu yazmadan testleri yazmak anlamsız gelebilir. Ama aslında öyle değil…
TDD’nin bir başka zorlayıcı kavramı ise testlerin kapsamı. “Test Coverage” olarak nitelendirebileceğim, test kalitesinin de ölçülmesi için bir kriter olan bu kapsam kavramı, “yaa, her şeyin testini nasıl yazacağım, o kadar zamanım yok, uğraşamam” gibi engellere takılacaktır. Bu engellere ne kadar takılırsanız, çok farkında olmadan “Teknik Borç“lanmaya o kadar girersiniz. Ne kadar çok test yazarsanız, o kadar az borcunuz olur. Ama tabi ki tüm kodlarınızı bu şekilde geliştirmeniz mümkün değil. Teoride mümkün olsa bile, pratikte bunu yapmanız pek mümkün olmayacaktır. Haa yapan varsa gerçekten, ayağa kalkıp, önümü ilikleyip alkışlıyorum… Ama test kapsamınızın büyümesi TDD konusunda tecrübenizin artması ile doğru orantılı olduğundan ne kadar çok denerseniz, o kadar çok “test coverage” artacaktır.
Peki nereden başlayacağız?
Kodu ya da testi yazmadan önce, ne amaca yönelik bir geliştirme yapacağımızı biliyor olmamız lazım. Sanırım bunda hepimiz hem fikiriz. Daha sonra bunu tasarlamamız gerekmekte. Kodu yazmadan önce testleri yazmanın zor olmasının sebebi acaba bu tasarımı yapmamış olmamız olabilir mi? Çok düşünmenize gerek yok, ben doğrudan “Evet” diyeyim. Geliştirme yapmadan önce eğer tasarımı oluşturmazsak, geliştirme süresi boyunca zorlanırız ve karmaşıklıklar ortaya çıkar. Ama bu geliştirme süresine yayılacağı için hemen farkına varabileceğimiz bir durum olmaz. Kod kalitesi, geliştirme süresi boyunca gittikçe düşer, ve son çıktımız da bir çok “bug”ı içerisinde bulundurur. Daha da kötüsü, bu “bug”ların hiç birinin farkında olmayız. Taa ki “object reference not set to an instance of an object” tokatını yiyene kadar…
TDD ise, tasarım eksikliği ya da yanlışlığını ilk anda bize belli eder. Bundan dolayı aslında zorlanıyoruz. Yazılacak kodun tasarımını öncede yaparsak, elimizde kod olmasa bile zaten neyi test edeceğimiz ortada olacaktır. UML ya da kağıt-kalem ikilisini kullanarak tasarımlarınızı yapabilirsiniz. Sadece yapın yeter. Tasarımdan sonra zaten gerisi kendiliğinden gelecektir.
Peki gerisi ne?
Tasarımı yaptığımız zaman elimizde kod olmasa bile, neyin kodu olacağını artık kestirebiliriz. Dolayısıyla testlerimizi de yazabiliriz. Operasyonel ihtiyaçlarımız doğrultusunda, tasarımda ortaya çıkan metotların ve sınıfların testini yazmak ikinci adım. Bu noktada metotların, girdi ve çıktılarını tanımlamış oluyoruz aslında. Yani ReverseString() şeklinde bir metot ihtiyacımızın hangi parametreler ile ne çıktı vermesi gerekliliğini bu aşamada dikkate alıyoruz. “Merhaba” parametresini verdiğimiz zaman “abahreM” çıktısını beklediğimiz yazacağımız testlerde belirtiyoruz. Burası aslında çok önemli. Çünkü ilk kodumuzu yazıyor olsak, nasıl parametreler geleceğini kestiremeyebiliriz. Belli bir tecrübeye sahip kişiler artık ilk seferde “NULL” kontrolleri yapıyor olsa bile, benzer kontroller büyük bir ihtimal atlanacaktır.
Tabii ki daha kodumuz olmadığı için testlerimiz ilk seferde hata alacaktır. Bu hataları çözmek için(!) artık kodu yazmaya başlayabiliriz.
Kodumuzu yazıyoruz…
Testleri ilk çalıştırdığımız zaman başarısız olacaktır, olmalıdır. Kodu yazarken bu başarısızlığı gidermek amaçlı yazmak kesinlikle çok tehlikeli. Bu yüzden sakın, testin başarılı olmasına yönelik düşünerek kod yazmayın. Kodu yazarken, tamamen ihtiyaca yönelmek daha doğru olacaktır.
Testi başarılı bir şekilde çalıştırmaya yönelik bir geliştirme yaparsak ise, kodun içeriği bu sefer ihtiyacı doğru karşılamayabilir. Ve yazmış olduğumuz test de anlamını yitirir. Yazmış olduğumuz testler, her ne kadar kodu test ediyorsa da, aslında kodun istenen ihtiyacı karşılayıp karşılamadığı test ediliyor.
Tekrar test…
Kodumuzu yazdıktan sonra, yazmış olduğumuz testleri tekrar çalıştırıyoruz, eğer testler başarılı olarak sonuçlanırsa, yeni testler ve yeni kodlara devam edebiliriz. Test başarısız sonuçlanırsa kodumuza geri dönüp, “refactoring” yaparak kodumuzu iyileştiriyoruz. Sonra yine test… sonra yine test… hep test, hep test.
Bu adımları, sırayla tek tek uygulayarak geliştirme alışkanlığınızı geliştirebilirsiniz ve hatta değiştirebilirsiniz. Bu adımlar bir döngü olarak ele alındığında, ihtiyaçlardaki değişiklikleri kodumuza aktarmak da oldukça kolay olacaktır. İhtiyaçlar değiştiği zaman tasarımınız güncelleneceği, tasarımınız güncelleneceği için de, yeni testleriniz ortaya çıkacaktır. Bu sayede kodunuzdaki değişiklikler de kontrollü bir şekilde geliştirilecektir. Kişisel fikrim eğer yapabiliyorsanız, tasarım ve testleri de ayrı olarak versiyonlamanız. Bu sayede komple yazılım projenizdeki değişiklikleri teknik olarak daha kolay yönetebilirsiniz.
Açıkcası bu adımları çok sağlıklı işletemiyorum kendi çalıştığım projelerde. TDD, direkt geliştirme kültürü ile alakalı bir durum. Dolayısıyla, yazılım geliştirme hayat döngüsü, kurum kültürü ile geliştiyse, TDD uygulamak biraz zor olacaktır. Ama bence bu zorluk bir bahane olmamalı. Küçük projeler ile başlayıp, büyük projelerin de bazı parçalarında yavaş yavaş uygulamaya çalışmak, başlamak için iyi olabilir.
Bir başka nokta da, her projenin TDD yöntemi ile geliştirmeye uygun olup olmadığıdır. Bu sorunun cevabını açıkcası bilmiyorum. Projeye göre değişiklik göstermesi biraz garip gelse de, projenin dinamikliklerinden dolayı, büyüklüğü, geliştiren ekibin yetkinliği ve proje yönetim metodlarından dolayı tercih edilmemesini anlarım. Ama tercih edildiği zaman, ilk yukarıda bahsetmiş olduğum “Technical Debt” kavramı için borcunuz daha az olacaktır. Kod bakım ve onarım eforlarınız düşecektir.
8 Comments
Akif Yanbak
29 Mart 2015 at 23:35Elinize sağlık
Akın Kaldıroglu
30 Mart 2015 at 15:40TDD yeni ve orijinal bir kavram ve yaklaşım olmakla birlikte nihayetinde yazılım babalarının “reponsibility-driven design” ya da “interface-driven design” dedikleri ve önce tasarım yaparak sorumluluklar üzerinden sistemdeki nesnelerin arayüzlerini ortaya koyma şeklinde ifade ettikleri yaklaşıma dayanıyor. TDD’nin testleri önce yazma tarafının gerçekleşebilmesi sizin de güzel bir şekilde belirttiğiniz gibi imkansız falan değil sadece düşünme ve çalışma şeklimizi değiştirmemiz gerekiyor.
Teşekkür ederim güzel yazı için.
Serdar Karaçay
31 Mart 2015 at 09:05TDD komplex method ve sınıflarda zorlanıyor.
TDD den önce SOLID gibi,tasarımı küçük küçük parçalamak gerekiyor.
TDD o vakit kolaylaşıyor.
Fikirleriniz için teşekkürler…
Ahmet
31 Mart 2015 at 09:06Tasarım yerine Analiz desek daha doğru bir yaklaşım olabilir diye düşünüyorum. Tasarım deyince ekran tasarımı vb de anlaşılabilir. Ancak buradaki Analiz kavramı da high level analize kadar olan ; kapsamın çıkartılması, use case lerin belirlenmesi, akışın belirlenmesi gibi behavioral modellemelerin yapılmasını kapsamalıdır. Bu noktada programcı business olarak ne yapacağının kabaca resmini çıkartmış olur. Bundan sonrası için TDD kullanılabilir.
Ülkemizde test kavramı yazılım geliştirme döngüsünde iş sahipleri tarafından – genellikle- ekstra olarak görüldüğü için maalesef verilen proje sürelerinden ilk çıkartılan veya süresi kısaltılan ilk yerlerden biri olarak görülmekte. Unit testlerin yazılması konusunda bile sıkıntılar yaşanırken TDD kavramının yaygınlaşmasının daha sancılı olacağı görülmekte. Tabi ki burada kurum kültürü en büyük etkenlerden biri.
Paylaşım için teşekkürler..
Mustafa TABUR
31 Mart 2015 at 22:14Emeğiniz ve paylaşımınız için teşekkür ederim.
İyi çalışmalar
Sinan BOZKUŞ
1 Nisan 2015 at 17:48Faydalı bir yazı olmuş, elinize sağlık.
Normalde hiç test yazmadan direk işe girişen biri olarak bir kaç örnek kod yapısı verseydiniz daha mantıklı olurdu düşüncesindeyim Öğrendiklerim biraz ezber kaldı gibi.
Uğur
15 Aralık 2015 at 11:59Yazı gayet açıklayıcı olmuş teşekkürler. Fakat Tekrar Test başlığı altında yer alan şu cümlenin yeniden ele alınması gerektiğini düşünüyorum:
> Test *başarısız* sonuçlanırsa kodumuza geri dönüp, “refactoring” yaparak kodumuzu iyileştiriyoruz.
Refactoring işlemi, yazıda yer alan görselde olduğu gibi, testin *başarılı* olduğu aşamada yer almakta ve artık çalıştığından emin olduğumuz kodun daha temiz hale getirilmesi amaçlanmaktadır.
Umut Çağdaş Coşkun
12 Ocak 2017 at 08:52Teşekkürler yazı için, elinize sağlık.