Test Güdümlü Yazılım Geliştirme’ye Merhaba

“Test Driven Development” yani Türkçe söylersem “Test Güdümlü Yazılım Geliştirme” ile birkaç ay öncesinde tanıştım. Fakat bu konuda halen öğrenmeye devam ediyorum. Bu konuda bildiğim temel bilgileri paylaşmak istiyorum. Aynı zamanda bu konuda kendimi geliştirdikten sonra geri dönüp düşüncelerime tekrar bakmak istiyorum. Aslında blog tutmamın en sevdiğim yanı da budur. Yazılım ile ilgili düşüncelerim sürekli değişmektedir. Bu değişimi takip etmenin bir yolu da blog yazmaktır.

Öncelikle yazılım yapmak demek sadece kod yazmak anlamına gelmez. Kod yazmak işin sadece bir kısmıdır. Yazılımda testler de oldukça önemlidir. Çünkü yazdığınız kodların düzgün çalışıp çalışmadığını ancak testler ile anlayabilirsiniz. Eskiden yazılım geliştirmede “Şelale (Waterwall)”  yöntemi uygulanıyordu. Bu konuda Java şampiyonumuz Özcan Acar’ın yazısını okuyabilirsiniz. Şelale yöntemi bir inşaat mühendisi için uygun olabilir, fakat biz yazılım mühendisleri için iyi bir yöntem değildir. Çünkü kimse bina yapıldıktan sonra inşaat mühendisine bu balkon burada güzel durmadı yıkıp yeniden şu tarafa yapalım demez. Ama yazılımda bizim karşımıza buna benzer şeyler defalarca çıkar. Bunun için yazılımda çevik yöntemler uygulanır. Test güdümlü geliştirmek çevik yöntemlerin önemli bir parçasıdır. Şelale yönteminde önce analiz sonra tasarım yapıldıktan sonra kodlama işine geçilir. Kodlama tamamlandıktan sonra testler yapılır ve daha sonra entegrasyona geçilir. Fakat projeler için verilen süre azalmaya başlandığında testler göz ardı edilmeye başlar. Test edilmeyen kodlar müşteriye teslim edilir. Testler tam anlamıyla yapılsa bile pek sağlıklı olarak gerçekleşmeyebilir.

Başta ben herşeyin önceden yapıldığını ve kod yazmanın işin hep son aşaması olarak düşünürdüm. Kendimi geliştirmek için yazmaya başladığım projelerde öncelikle detaylı bir tasarım yapardım. İnşaat mühendislerinin yaptığı gibi yazılımı önce çizip tasarlayarak sonra kodlamaya geçerdim. Tasarım prensiplerine hakim olmamamın da etkisi olsa da hep hesaplamadığım şeyler ortaya çıkıyordu. Tasarımım da esnek olmadığı için projeyi kodlarken acayip rahatsızlık hissediyor ve bir sürü kötü kodlama yapıyordum. Bu da beni olağanüstü mutsuz ediyordu. Birşeylerin eksik olduğunu farkettiğimde tasarım ile ilgili dökümanlar aramaya başlamıştım. Bu sayede Özcan Acar ile tanışma fırsatı buldum. KurumsalJava blogunu incelediğimde Özcan Acar’ın test güdümlü geliştirmenin çok önemli olduğunu yazılarında ve seminerlerinde gördüm. Sadece tasarımda değil öğrenmem gereken bir sürü şey olduğunu ve hatta daha yolun başında bile olmadığımı farkettim :)

Birim testleri için Java da JUnit kullanılıyor. JUnit ile ilk Linux yaz kampında tanışmıştım. Fakat orda test güdümlü yazılım yapmadık sadece yazdığımız kodları test ettik. Test güdümlü yazılım geliştirmek demek sadece yazılan kodları test etmek anlamına gelmez. Yazılımda niyetiniz önemlidir. Kodlamaya başlamadan önce testler yazılır. Daha sonra bu testlerin çalışması için gereken kodlar yazılır. Bu sayede adım adım kodlama yapılmış olur. Bu başlangıçta zaman kaybı gibi gözükebilir. Çünkü yazılımın gereken kodların yanı sıra bir de test yazmak zorundasınız. Bazen iki yazılımcı aynı iş için çalışır. Bir yazılımcı testleri yazarken diğeri de testlerin çalışması için gereken kodları yazar. Test yazmak başlangıçta zaman kaybı olarak görülebilir olsa da aslında durum tam tersidir. Test yazdığınız zaman yazdığınız kodlar %100 test edilmiş olur. Bu sayede yaptığınız yazılım sorunsuz çalışır. Otomatik çalışan testleriniz olmadığı zaman kodları test etmek bir hayli zordur. Bu da zaman kaybettirir. Bunun yanı sıra gereksiz kodlar yazmanızı engeller. Bu sayede yine zaman kazanmış olursunuz. Düşünün bir sınıfınız var ve o sınıfa refactoring uyguladınız veya optimizasyon yaptınız. Yaptığınız değişikliklerin eskisi gibi düzgün çalıştığını nasıl anlarsınız? Bunun için testleri çalıştırmanız yeterli olacaktır. Yazılımınız daha tutarlı bir hale gelmiş olacaktır. Özellikle refactoring yapmak için otomatik çalışan testlerinizin olması şarttır.

Paul Graham’ın “Hackers and Painters” kitabından bir alıntı yapmak istiyorum: “Programlar yazılırken şekillendirilmelidir; tıpkı yazarlar, ressamlar ve mimarların yaptığı gibi.” Bu söze kesinlikle katılıyorum. Programları yazarken şekillendirmek için ise öncelikle amacımızın yani niyetimizin belirli olması gerekir. Test güdümlü yazılım geliştirmek aynı zamanda bunu sağlamış olur.

Test güdümlü yazılım ile ilgili bildiklerimi aktardıktan sonra bir örnek vermek istiyorum. Çok sevdiğim bir kod katası olan Roman Rakamları katasından örnek vereceğim. Dilerseniz http://kodkata.com/roma-rakamlari-katasi/ adresinden katayı izleyebilirsiniz. Kata hakkında bilgi almak için ise buraya tıklayabilirsiniz.

Öncelikle RomanNumeral sınıfı için test sınıfı (RomanNumeralTest) oluşturuyoruz ve bir test metodu yazıyoruz.

Anlaşılacağı gibi “I” değerini verdiğimde sonuç olarak 1 değerini döndermesi gerekir. Bunu için assertThat metodunu kullandım. İlk parametre olarak test etmek istediğimiz metodu, ikinci parametre olarak beklediğimiz değeri giriyoruz. Burda kodun okunmasını kolaylaştırmak amacı ile CoreMatchers.is ve CoreMatchers.equalTo  statik metodlarını kullandım.

Henüz ortada RomanNumeral adlı sınıfımız yok. Öncelikle bu sınıfı oluşturup içerisine convert metodunu oluşturuyoruz. Test kodlarını çalıştırdığımızda başarısız olduğunu görüyoruz. Bu testi geçmek için çok detaylı diğer roman rakamlarını da düşünerek kod yazmıyoruz. Sadece bu testin gerçekleşmesi için gereken kodu yazıyoruz. Bunun için return 1 ifadesi yeterli olacaktır.

return 1 ifadesini eklemek testlerin başarılı olmasını sağlayacaktır. Bunu gerçekleştirdikten sonra 2. bir test yazıyoruz.

Burda dikkat ederseniz roman nesnesini yerel değişken olarak kullanmak yerine sınıf değişkeni yani “field” olarak kullanıyorum. Çünkü aynı kodu her test için yazmak zorunda değiliz. Bu sayede DRY(Don’t Repeat Yourself) prensibine uymuş oluyoruz. 2. testimizde gördüğünüz gibi “V” değerini gönderdiğimizde 5 değerini almak istiyoruz. Yukarda bahsettiğim gibi öncelikle niyetimizi yazıyoruz. convert metodunda değişiklik yaparken bu sefer her 2 testin de çalışmasına dikkat etmemiz gerekiyor. O yüzden RomanNumeral sınıfımız aşağıdaki gibi olacaktır.

metodumuza bir if ekleyerek 2 testin de başarılı olmasını sağladık. Aynı şeyi “X” -> 10 için yapıyoruz.

 

3. testimizi yazıp gereken değişiklikleri yaptık. convert metoduna baktığımızda istenmeyen bir durum görüyoruz. Her yeni test için sürekli bir koşul giremeyiz. O yüzden RomanNumeral sınıfını aşağıdaki şekilde düzenliyoruz.

Yaptığımız değişiklik sayesinde convert metodu tek satıra inmiş oldu. Bu değişikliğin düzgün çalıştığını görmek için testleri çalıştırmamız yeterli. Yalnız burada RomanNumeralTest sınıfında yine sürekli tekrar eden kodlar var. Her test için yeni bir metod ekliyoruz. Yaptığımız iş sadece istenen değeri girip beklenen bir sonucu karşılaştırmak. JUnit bizim için bu konuda kolaylık sağlıyor. RomanNumeralTest sınıfını aşağıdaki şekilde yeniden düzenliyoruz.

Burdaki değişikliği kısaca anlatmam gerekirse öncelikle convert_as_expected isimli bir metod ekliyoruz. Bu metod yapacağımız tüm testleri için genel bir yapıyı oluşturuyor. Burada farkedeceğiniz diğer nokta input ve expected sınıf değişkenleri ve constructor metod. JUnit constructor metodu kullanarak convert_as_expected metodunu çağıracak ve bu sayede testi gerçekleştirmiş olacaktır. convert_as_expected metodu için gereken verileri içeren bir static metod (testData) ekledik. testData metoduna aynı zamanda @Parameters annotation’unu ekledik. Bu metod bir Collection<Object[]> nesnesi dönderecektir. JUnit bu nesnedeki verilerle otomatik olarak RomanNumeralTest nesnesi (constructor ile)  oluşturacak ve bahsettiğimiz convert_as_expected metodunu her eleman için çağıracaktır. Bunu sağlamak için ise sınıfın başına @RunWith(Parametrized.class) ifadesini ekledik. Testi çalıştırdığınızda testData metoduna girdiğiniz verilerin de test edildiğini görebilirsiniz. Bu yüzden önceki 3 metoda gerek kalmadı. Çünkü bu metodların yaptıkları işi bu şekilde sağladık. Artık yeni bir test eklememiz gerektiğinde testData metodundaki listeye bir değer eklememiz yeterli. Bunun için öncelikle “VI” değerini girelim ve 6 değerini bekleyelim.

 

Gördüğünüz gibi yeni bir test eklemek için tek satır eklememiz yeterli oldu. Testi çalıştırdığımızda başarısız olduğunu göreceksiniz. Bunun için aşağıdaki düzenlemeyi yaptım.

Yine aynı basit bir şekilde bir koşul eklememiz yeterli oldu. Sırada II roman rakamı var. Bunun için yeni bir test kodu yazıyoruz.

Buna karşılık convert metodu aşağıdaki şekilde değiştiriyoruz.

Yine basit bir şekilde bütün testlerimizin yeşile dönmesini sağladık. Burada digits.get(roman) ifadesini birçok yerde kullandığımızdan toNumber() metodunu ekledim. Sırada 3 basamaklı roman rakamları var. Bunun için “VII” roman rakamını test ediyorum. RomanNumeralTest sınıfını tekrar buraya yazmıyorum, ne yapılması gerektiğini anlamışsınızdır zaten. “VII” için kodu tekrar düzenlediğimizde convert metodu aşağıdaki hali alacaktır.

Bu sayede testimizin başarılı olmasını sağladık. Son olarak “IV” roman rakamını test ediyoruz. RomanNumeralTest sınıfımızın tamamı aşağıdaki şekilde olacaktır.

RomanNumeral sınıfımızım tamamı da aşağıdaki gibidir.

Katanın devamını kodkata.com adresinden izleyebilirsiniz. Gördüğünüz gibi basit bir şekilde test güdümlü yazılım yaptık. Test güdümlü geliştirme sayesinde daha rahat kod yazdığımı farketmiştim. Umarım aynı rahatlığı siz de hissedersiniz. Test güdümlü yazılım geliştirmek yazılım geliştirmeyi daha zevkli hale getiriyor. Tabiki bu benim düşüncem :) Test güdümlü yazılım geliştirme öğrenmeye devam ediyorum. Elimden geldiğince öğrendiklerimi burada paylaşacağım.

Sonraki yazıda görüşmek üzere…