Yazılım geliştirmenin Analyis, Design, Development, Testing , Integration, Maintenance şeklinde giden bir cycleı bulunmaktadır ve bu fazların her birinin belli bir maliyeti bulunuyor. Biz yazılımcılar doğamız gereği Design ve Developmenta focuslanmış durumdayız. Ancak costlara baktığımızda şaşırtıcı bir şekilde aslında yazılım geliştirmede Design + development maaliyetinin %10 seviyelerinde olduğunu görüyoruz. Testing ve implementation ise bunların daha üzerinde bir maliyete sahip. Maintenance ise bunların tamamının üzerinde %65lik bir malyet oranına sahip.
Peki Maintanenceyı bu kadar yüksek maliyete sahip yapan etkenler nelerdir. Zaman içerisinde değişim ihtiyacı duyulmayacak bir yazılım sistemi düşünülemez. Kullanıcı ya da müşterilerin ihtiyaçlarındaki değişimlerin sisteme yansıtılması gerekir. Ayrıca, yeni bir donanım ya da yazılım altyapısı nedeniyle sistemin çalışma koşulları değişebilir. Tabii ki testler sırasında farkedilmeyen hatalar tespit edilebilir ve giderilmesi gerekir.
Maintanence’ı zorlaştıran bazı sebepler…
Kodlama yapılırken design aşamasının eksikliğinden kaynaklı belli yazılım prensiplerinin gözardı edilmesiyle oluşan kodun bakım ve geliştirmesinin zorlaşması.
Kodun tek veya bir kaç kişinin tekelinde olacak şekilde geliştirilmesi ve belli sebeplerden dolayı bu kişilerle herhangi bir sebepten iletişim olmaması.
Kod değişiminin getireceği riskler.
Legacy code
Legacy Code Nedir?
Legacy code’u kavram olarak 4-5 yazılımcıya sorsanız 4-5 farklı cevap ile karşılaşabilirsiniz. Bu cevapları kısaca bir araya getirirsek.
Artik desteklenmeyen yazilimlara / dillere dayanan, ama hala kullanimda oldugu icin zorunlu olarak devam ettirilmesi ve bakim yapilmasi gereken kod.
Bir yazılımcıdan başka bir yazılımcının kontrolüne geçen kod.
Testi bulunmayan kod.
Automated testi bulunmayan kod.
kavramları legacy kodu işaret eder. Legacy codelar değiştirildiği takdirde hata çıkarmaya daha meyillidir.
1.Test Driven Development Dünyası
1.a ) Yazılım Dünyası içn Test Nedir
Test driven developmenta geçmeden önce yazılım dünyası için testin ne ifade ettiğinden biraz bahsedelim. Test kısaca kodunuzun beklenilen ihtiyaçları karşılayıp karşılamadığını doğrular. Testin işlevlerini kısaca gruplandırmak istersek..
Kod istenen ihtiyaçları karşılıyor mu,çözmesi istenen problemleri çözüyor mu?
Tüm inputlara doğru yanıt veriyor mu?
Kod istenilen performansta mı?
1.b )Test Tipleri
a.Unit test : Unit test yazılımın en küçük birimi olan fonksiyonların bir bir hızlıca test edildiği test şeklidir. Unit testte bir fonksiyonun, diğeriyle entegre çalışmasından ziyade o fonksiyonun kendi başına nasıl çalıştığına bakılır.
b.Integration Test : Birden fazla componentin bir arada uyumlu bir şekilde çalışıp çalışmadığını test eder. Unit test ve Integration test arasında genellikle en sık yapılan hata ise: Test Güdümlü Yazılım (TDD) geliştirirken, birim testleri yazmak için başlayıp sonrasında ise fark etmeden entegrasyon testinin yazılmasıdır. Oysa entegrasyon testleri, yukarıda da bahsettiğim gibi birbirleri ile bağlı bir şekilde çalışan bir kaç modülün bir araya gelerek çıkardıkları sonuçla ilgilenirken, birim testleri ise sadece birim başı testlerle ilgilenir.
c.Acceptence test : Kullanıcı kabul testi demektir. Yazılan bir programın ya da üretilen bir ürünün son kullanıcı ya da kullanıcılar tarafından test edilip değerlendirilerek onayının verilmesi sürecini içerir.
1.c) Black Box Test – White Box Test
Black-box test, kod ya da tasarım ile ilgilenmez. Testlerin fonksiyonellik içerisine erişim yetkisi yoktur. Back-box test ile belli inputları vererek buna karşılık doğru outputları alıp almadığımızla ilgileniriz. Black-box test aynı zamanda, Fonksiyonellik testi, Closed Box testi ya da Opaque testi olarak adlandırılır. Ancak çok fazla bağımlılığı olan projelerde black-box test kullanmak sıkıntılara yol açabilir.Kara kutu testinin en büyük avantajları:
Testler hızlıca icra edilebilir.
Testi yapan kişi ya da kişiler, ilgili yazılım birimini(programlama dili, yazılım modülü, işletim sistemi vs) bilmek zorunda değildir.
Testler objektif bir şekilde icra edildiğinden, aynı zamanda son kullanıcı perspektifi ile de test edilmiş olur.
White-box testte ise kodun içerisini görebilir , componentlerin birbiriyle ilişkisini monitor edebilir hatta test spesifik dependencyleri inject dahi edebiliriz. Çok fazla dependency ve interconnected yapıların olduğu yerde idealdir. Ancak eğer ki kodun herhangi bir yerinde değişiklik yapmak istersek, bu tip testlerin çoğu break olur çünkü yeni refactor yapılan kodla noncompatible olur.
1.d) Test Driven Development Nedir?
TDD, kod yazılmadan önce test senaryolarının yazılması, bu senaryolara bağlı olarak kodun yazılması temeline dayanan bir yaklaşımdır. Test driven development sürecini anlayabilmek için öncelikle Red cycle, Green cycle ve Refactor kavramlarını anlamamız gerekmektedir.
Red Cycle Nedir? (Hatalı Sonuç Döndüren Test)
Bu durum, geliştireceğiniz uygulamaların hangi durumlarda hata vereceğini kestirip bu durumlarda nasıl sonuçlarla karşılaşacağınızı öğrenmenizi sağlayacaktır. Örnek uygulamamız da hesaplama işlemleri var ise ve bu hesaplama işlemlerinin birindeki bölme işleminde bölenin 0 olma durumu söz konusu ise bu durumu ortaya çıkartan bir senaryoya uygun test metodu hazırlayacaksınız. Hazırladığınız testi çalıştırdığınızda olmasını beklediğiniz durumla, yani hatayla karşılaşmanız gerekmektedir. Hata oluşmadığı durumda kodda bir yanlışlık var demektir.
Green Cycle Nedir? (Başarılı Sonuç Döndüren Test)
Green Cycle, başarılı sonuçlar ortaya çıkartan test durumları yazma işlemidir. Bu test sonucunda ortaya çıkan değer ile çıkmasını düşündüğünüz (olması gereken) değerin aynı olması (ya da ilgili koşula uygun olması) beklenmektedir.
Refactor Nedir? (Kod Temizleme)
Refactoring, testlerden olması gereken sonuçları elde ettikten sonra yazdığınız kodları daha basit, anlaşılır ve geliştirmeye açık (daha kolay düzenlenebilir) bir hale getirme işlemidir. Refactoring uygulanmış bir kod daha temiz olduğu için okuması daha kolaydır.
Şimdi TDD’yi uygulama aşamasına geldiğimizde nasıl bir yol izlemeliyiz sorusuna cevap aramaya çalışalım.
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 gerekiyor. Aslında kodu yazmadan önce testlerini yazmanın kafada karışıklık yaratmasının sebebi geliştirmenin tasarlanmamış olmasından ileri gelir. Siz daha ortada bir kod yokken onun testini yazabiliyorsanız yapacağınız şeyi kafanızda gerçek anlamda kurgulayabilmişsiniz demektir.
Önce Fail olacak testler yazılır. Test edilen sınıf metotları ilk etapta null değerini geri verecek yada hiç bir şey yapmayacak şekilde programlanır. Tabii ki daha kodumuz olmadığı için testlerimiz ilk seferde doğal olarak fail alacaktır, almalıdır da. Bu hataları çözmek için(!) artık kodu yazmaya başlayabiliriz. Kodu yazarken, tamamen ihtiyaca yönelmek daha doğru olacaktır.
Kod testleri geçecek duruma getirilir. 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.
Kod refaktör edilir. Eğer tüm testleri green durumuna getirebilirsek, 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.
Ayrıca TDDyi unit test ,integration test ve acceptance test üzerinde ihtiyaçlarımıza göre white box testing veya block-box testing olarak inşa edebiliriz.
Test Hassasiyeti
Yukarıdaki resmi baz alarak sol tarafta ürün kodumuzun, sağ tarafta ise testlerimizin olduğunu varsayalım. Kodlarımızla testleri arasında sıkı bağımlılık olmasını istemeyiz. Örneğin, ürün kodunda bir fonkisyon içerisinde bir değişiklik yapıldığında(kırmızı alan) testin bu değişiklikten etkilenmemesini, aynı şekilde test kodunda bir değişiklik yapıldığında ürün kodunun da buna paralel değişiklik gerektirmemesini isteriz. Eğer testteki bir değişikliğe paralel ürün kodunda veya ürün kodundaki değişikliğe paralel test kodunda bir değişiklik yapmamız gerekirse , değişikliklerimizde iki tarafa da uygulanan buglar olduğunu nasıl fark edebiliriz ?
TDD uygulandığında sağladığı avantajları şu şekilde sıralayabiliriz:
Test Driven Development sizi basit, kullanıcının gözünden, diğer sınıflara bağımlılığı az olan, kodun sorumlulukları ayrılmış, tekrar içermeyen kod yazmaya zorluyor. Bunlarda kodun kalitesi açısından oldukça önemli faktörler o yüzden sizde denedikçe ne kadar verimliliğinizin, yazdığınız kod kalitenizin arttırdığını göreceksiniz.Fakat iyi oturtulmuş bir yazılım sürecinde Continuous Integration(Sürekli Birleştirme), Integration Tests(Entegrasyon Testleri), Acceptance Test(Kabul Testleri) ile birlikte kullanıldığında projenizin üzerindeki gerçek kalite etkisini daha da belirgin göreceksiniz.
Programcı testlerini hazırlarken, sistemin nasıl çalışması gerektiğini hayal etmek zorundadır. TDD ile sadece gerekli sınıflar ve metotlar oluşturulur. TDD, programcının “belki ilerde kullanılır, bu metodu eklemekte fayda var” tarzı düşünmesini engeller. Böylece TDD proje maliyetini düşürür, çünkü sadece gerekli sınıf ve metotlar için zaman harcanır.
TDD ile test kapsama alanı (test coverage) geniş olur. Hemen hemen her satır kod test metotları tarafından çalıştırılır.
Testler koda olan güveni artırır. Kod üzerinde yapılan değişiklikler yan etkilere sebep verebilir. Testler olmadan oluşabilecek yan etkilerin tespiti çok zordur.
Testler sistemin nasıl çalıştığını gösteren dokümantasyon olarak düşünülebilir. Programcılar testleri inceleyerek, sistemin nasıl çalıştığını çok kısa bir zaman içinde öğrenebilirler.
TDD tarzı programlama programcının debugger ile hata arama zamanını kısaltır yada tamamen ortadan kaldırır.
Design first mentalitesi kazandırır.
Over-engineeringten kaçınmamızı sağlar.
Proaktif çalışma sayesinde, sıkıcı bir geliştirme ortamı yerine, eğlenceli ve güvenli bir ortamda motivasyonu yüksek ekiplerin oluşturulabilmesine sebep olur.
TDD GELİŞTİRME YAPILIRKEN UNUTULMAMASI GEREKENLER
Test kodlarımız da normal kodlar gibi ele alınmalı ve üvey evlat muamelesi yapılmamalıdır. Gelişime açık kodlar tasarlanmalıdır.
Testler yazılırken pozitif caseler ele alındığı gibi negatif caseler de ele alınmalıdır.
Testler yazılırken Neccesary value ve resultlara odaklanılmalıdır. Testler bir işin nasıl yapıldığından çok ne yapılmaya çalışıldığıyla ilgilenmelidir.