Category Archives: Yazılım Testleri

Yapay Zekaya Güvenmek

Yapay zeka ile uygulamayi cikardiniz ama kodun durumu hic icinize sinmedi mi?

Akliniza bircok soru geliyor olabilir. Örnegin yük altinda stabil calisak mi? Paralel islemler veriler üzerinde hata izleri birakacak mi vs. Eger akliniza hicbir soru gelmiyor ve uygulamayi hemen canliya almak istiyorsaniz, bu yaziyi önce okuyun derim :)

Yazilim sadece kodun calisiyor olmasi ile bitmez. Localhost üzerinden tek bir kullanici ile test edilmis bir uygulama henüz hicbir seye hazir degildir. “Lokalde calisiyor ama” söylemlerinin ana sebebi de budur.

Yapay zekanin ürettigi kodu nasil güvenilir hale getirirsiniz? Burada benim gibi artik hic kod yazmadiginizi farz ediyorum. En alttan yukariya dogru atilmasi gereken adimlari siralayacagim.

## Birim Testleri
Her prompt icinde mutlaka “test etmeyi de unutma” ibaresi olmali. Yapay zeka olusturdugu her satir kod icin karsiligi olan bir birim testi yazacaktir.

## Entegrasyon Testleri
Bu testler entegre edimis bir sistem de (api + db) mevcut apilerin onay/kabul kriterlerine uygunlugunu test ederler. Bu tür testlerde yapay zeka tarafindan olusturulmalidir.

## Code Coverage
Mevcut testlerin ne oranda kodu kapsadigini anlayabilmek icin code coverage araclari ile ölcüm yapilmasi gerekmektedir. Ben yapay zekaya arada bir “kod kapsama seviyemiz” nedir diye soruyorum. Aldigim cevap genelde tüm sistem geneli icin gecerli olan %90 civarinda oluyor. Ayrica yapay zekadan mevcut testleri inceleyerek, hangi alanlar icin test yazilabilecegi analizini istiyorum. Bu sekilde zaten eksik olan tüm birim testleri ortaya cikmis oluyor.

## Onay/Kabul Testleri
Yazilimci bu noktadan itibaren uygulamayi test etme islemini kendi eline almak durumundadir, cünkü uygulamadan olan beklentilerin onay/kabul kriterleri ile net olarak ifade edilmeleri gerekmektedir. Burada yine given/when/then tarzi onay/kabul kriterlerinin yer aldigi tanimlamalar ihtiva eden test spec dosyalari olusturulur. Bu spec dosyalarinin test koduna dönüstürülmesi gerekmektedir. Burada yine yapay zekadan bu spec dosyalarindan test kodunu olusturacak minik bir test catisi olusturmasi istenir. Buradaki en büyük avantaj beklentilerin plain text ile ifade edilmesi lakin gerekli test kodunun olusturulma isleminin yapay zekaya birakilmadir. Test kodu dogasi itibari ile cok kirilgan bir yapiya sahiptir. Yazilimcinin test kodu yazmak yerine, beklentilerini ifade ettigi spec dosyalari olusturmasi, kirilgan olan test kodun bakim yükümlülügünü yapay zekaya aktarmaktadir.

## Test Harness
Uygulamanin karmasik bir kismi oldugunu düsünelim. Ben burada kendi calismamdan bir örnek vereyim. Cloud main backend ile müsteri isletmelerinde benzer yapida calisan custom onprem backend sistemlerinin karsilikli olarak verisel senkronize edilmesi gerekiyor. Onprem tarafindan örnegin olusan siparislerin düzenlli olarak cloud main backende aktarilmasi lazim. Ayni sekilde cloud main backend üzerinde yapilan tüm degisiklikler verinin ait oldugu onprem instance tespit edilerek SSE üzerinden oraya aktarilmasi gerekiyor. Burada cok karmasik bir replikasyon implementasyonu var.

Bu yapiyi onbinlerce siparis ve benzeri verilerle test edebilmek icin yapay zekadan bir test harness catisi olusturmasini istedim. Bu test harness bünyesinde onbinlerce siparisi otomatik olarak onprem üzerinde olusturuluyor ve akabinde bu verilerin cloud main backende intikal edip, etmedikleri test harness catisi tarafindan kontrol ediliyor. Bu bir yük testi degil, ben sadece onprem veritabaninda olusan her satirin dogru bir sekilde cloud main backend aktarilmasini test etmek istedim.

Bu sekilde karmasik uygulama özellikleriin yük altinda ne kadar dogru ve stabil calistiklari test edilebilir.

Eger cikan sonuclara güvenmiyorsaniz, benim yaptigim gibi yapay zekadan iki veritabanini sync log dosyasi isiginda kiyaslamasini isteyebilirsiinz. Gün sonunda hakikaet veritabanindaki veride yatmaktadir.

## Yük Testleri

Load ya da performance testing icin bircok arac mevcut. Bunun icin yapay zeka destegi almak gerekmiyor diyebilirsiinz, ama cikan sonuclarin analizi icin yine yapay zeka kullanilabilir.

## Uygulama Loglari

Tek bir log yerine her büyük islem icin bir log dosyasi tahsis edilebilr. Benim uygulamada örnegin sync islemleri, siparis transaksiyonlari, merge islemleri, sse watchdog icin ayri birer log dosyasi var. Buna paralel tüm loglarin toplandigi ana bir log dosyasi da mevcut, lakin bir hata bulmak icin bu dosyayi yapay zekaya verip, contexti patlatmanin ve tokenleri yakmanin bir anlami yok.

Bunun yerine hem hata aramak hem de implementasyonu check etmek icin kücük log dosyalarini yapay zeka ile paylasiyorum. Bu sekilde cok implementasyon hatasi kesfedebildim.

## Baska Modelleri Ile Cross-Check
Ben implementasyon icin opus-4.6, analiz icin opus-4.7 kulllaniyorum. 4.7 kod yazma konusunda biraz ucuk, ama analizleri cok iyi. Bu yüzden analizleri yüksek modelle yapip, implementasyon icin bir alt modeli kullaniyorum. Bu sekilde opus-4.6 nin aklina bile gelmeyenleri, eksikleri ya da hatalari opus-4.7 ile kesfetmek ve tamamlatmak münkün. Bu yüzden “güven iyidir, ama kontrol daha iyidir” demek geliyor icimden :)

## Sürekli Entegrasyon
Her commit ile kodun derlenmesi ve mevcut testlerin kosturulmasi gerekir. Eger test kirilmalari varsa, bu yapisal bir degisikligin yan etkilerinin olduguna isaret eder. Bu yüzden sürekli entegrasyon (continuous integration) kullanilmasi gereken önemli süreclerden birisidir. Tüm testlerin calisiyor olmasi yeni bir sürüm olusturmanin ilk adimi olmalidir.

Test Yazmaya Gerek Kalmadi

Artik kodu yapay zeka yaziyor, bunu kabul ettik. Peki biz yazilimci olarak isin neresindeyiz? Bunu burada uzun uzun anlatmak istemiyorum. Yazilimci olarak isimiz baska alanlara kaymis durumda, ama artik kod yazmayacagiz.

Artik en önemli görevlerimizden birisi test yazmak / yazdirmak ve yapay zekanin olusturdugu kodun yani olusturmak istedigimiz ürünün davranislarini test etmek. Ama test kodunu da tamamen yapay zekaya birakmak, onun calip, onun oynadigi bizim ise sadece seyredici oldugumuz bir ortam dogurur. Bu da istedigimiz bir durum olamaz. Bizim girdileri ve ciktilari kontrol edebilecegimiz yeni bir soyutluk seviyesine ihtiyacimiz var.

Cogumuz “yapay zekaya testleri de yazdirabiliriz, hem de bunu bizden daha genis kapsamli yapar” diyecektir. Burada bazi problemler görüyorum:

  • Kodu degistiren yapay zekanin testleri de adapte etmesi tehlikeli ve bu kontrol edilemez sonuclar dogurabilir (kendi calip, kendisi oynar metaforu)
  • Statik test kodu sürekli kirilmaya müsait oldugundan, bakimi cok zordur. Yine burada “bunu yapay zeka halleder” demek uygun degil, cünkü elimizde tutmak istedigimiz bir kontrol mekanizmasina ihtiyacimiz var
  • Test etmek istiyoruz lakin kod yazmak, bakimini yapmak ve adapte gibi detaylar ile ugrasiyoruz. Bunun yerine nasil yerine ne sorusunu sormamiz gerekmektedir. Neyi test ediyoruz sorusu test etmek istedigimiz isletme mantigina odaklanmamizi saglayacaktir. Burada imperatif düsünce yerine neyin test edildigini tayin ettigimiz deklaratif düsünce tarzina gecmis oluyoruz.

Ne yapabiliriz?

Artik düsüncelerimizi belli kaliplara sokarak bilgisayar ile calisma zorunlulugumuz ortadan kalkmis durumda. Biz yapay zeka ile kendi dilimizde iletisim kuruyoruz. Ciktilari kontrol etmek icin kullanacagimiz yeni soyutluk seviyesi de bu.

Bir uygulamanin feature olarak tanimladigimiz ve implemente ettigimiz degisik türde davranislari olabilir. Bu davranislarin uygulama tarafindan sergilenebilmesi icin kullanicilar tarafindan tetiklenmeleri gerekir. Tanimli bir input gerekli davranis bicimi uygulandiktan sonra tanimli bir output doguracaktir. Programci olarak bu girdi ve ciktilari kendi dilimizde tanimlayabilir ve yapay zeka ya da test catisi tarafindan uygulamaya karsi kosturulmalarini saglayabiliriz.

Kendimden bir örnek vereyim. Ben testleri artik bir yml dosyasinda su sekilde specler olarak tanimliyorum:

name: 'Clean Start Test'
description: 'When SQLite-Database deleted, then app starts in default setup mode'

tags:
  - clean

setup:
  - 'Delete database'
tests:
  - name: 'App starts with setup screen'
    steps:
      - 'Start the app'
      - 'Wait 2 secs '
      - 'Expect that "Lütfen cihaz türünü seciniz" is visible'

Sonnet’e bu specleri okuyup (parse, analyse), teste dönüstürüp (generation), kosturabilen (execution) ufak capli bir framework yazdirdim.

Bu specler tanimlamis oldugum onay/kabul kriterlerini ihtiva ediyor. Bir nevi BDD (behaviour driven development) yapiyorum, ama onun ismi artik BDD degil, BDC (behaviour driven check). Testleri bu sekilde tanimladiktan sonra, kosturuyorum. Sonnet’in olusturdugu test framework testleri kosturmadan önce spec dosyalarini yapisal analiz yapiyor ve akabinde test kodunu olusturarak, bunlari kosturuyor.

Ben bir flutter uygulamasini test etmek icin bu yöntemi sectim. Bir web uygulamasini bu sekilde tamamen yapay zeka yardimi ile test etmek cok daha kolay. Playwright isminde bir MCP server mevcut. Onun yardimi ile Sonnet gibi bir LLM spec dosyasi icinde bulunan test adimlarini MCP araciligi ile bir web tarayicisina baglanarak kosturabilir.

Kisaca özetleyecek olursak:

  • Artik test kodu yazmiyorum ya da bakmini yapmiyorum.
  • Testlerim kirilmiyor.
  • Uygulamanin davranislarina odaklaniyorum ve neyin test edilmesini gerektigini tayin ediyorum.
  • Yapay zekanin sadece kodu adapte etmesine izin veriyorum, test kodunu (specler) degil. Bu sekilde neyin test edilecegi hakimiyeti bende kaliyor.
  • Yapay zekadan destek alarak genis kapsamli specler olusturabiliyorum.

Bu speclerin olusturulma adimini da su sekilde otomatize edecegim. Jira bünyesinde yeni bir feature tanimlarken onay/kabul kriterlerinin de tanimlanmasi gerekiyor. Ben bir kodlama ajanina bu feature i implemente etmesini söyledigimde, ajan ilk adim olarak gerekli test spec dosyalarini. olusturacak. Ajan isini tamamladiktan sonra test icin olusturdugu spec dosyalarini derleyerek, kosturuyor olacak. Bir hata ile karsilasmasi durumunda implementasyonun istenen seviyede olmadigini anlayarak, gerekli adaptasyonlari yapmaya baslayacak. Böyle bir döngü ile aslinda hic insan interaksiyonu olmadan bir uygulamayi feature by feature implemente ettirmek mümkün.

Test ekibine hos geldin yapay zeka :)

Temel Prensipler Yazı Serisi – Yazılımda Şemsiye Modeli

Yazılımda testlerin gerekliliğini ve hangi testlerin ne zaman uygulandığını açıklamak amacıyla şemsiye modeli ismini verdiğim bir model oluşturdum. Bu yazımda sizlerle bu modeli ve işlevini paylaşmak istiyorum.

Şemsiyeler gerçek hayatta yağmurdan ve yer yer güneşten korunmak için kullanılır. Yazılım süreçleri için oluşturduğum şemsiye modelinde yazılım testleri uygulamayı korumak için açılan şemsiyeleri temsil etmektedir. Şemsiyenin büyüklüğüne ve işlevine göre uygulamayı hatalara karşı korumak ve sağlıklı bir şekilde gelişmesini sağlamak mümkündür.

Şemsiye modelinde değişik büyüklükte şemsiyeler yer almaktadır. Açılan bir şemsiyenin kapsadığı bir alan mevcuttur. Bu alan şemsiyenin koruyabildiği alandır. Değişik test türleri değişik büyüklükte alanları koruyabilirler. Testleri olmayan bir uygulamayı şemsiye modeli ayazda ve yağmurda kalmış olarak tanımlar. Böyle bir uygulamanın akıbeti bellidir.

Ayazda kalmış bir uygulamaya göz atarak başlayalım. Resim 1 de böyle bir uygulama görülmekte.


Resim 1

Resim 1 de yer alan uygulamanın yağmurdan korunması yani hatasız olması ve kalabilmesi için hiçbir şemsiye açılmamış yani test oluşturulmamıştır. Böyle bir uygulamanın geliştirilmesi tamamen rus ruleti benzeri bir girişimdir. Testler olmadığı sürece yani bir veya daha fazla koruyucu şemsiye açılmadığı sürece bu uygulama su altında kalmak zorundadır. Projenin belli bir aşamasından sonra ekibin tek işi, su dolan teknenin batmasını engellemeye çalışmak olacaktır. Teknenin su dolmasını engelleyici bir mekanızma olmadığından, su tahliyesi bir şey ifade etmemektedir. Tekne er ya da geç batacaktır.

Şimdi her türlü hava koşulunda uygulamayı istenilen hedefe taşımak için atılması gereken adımlardan bahsetmek istiyorum. Şemsiye modelinde değişik büyüklüklerde, uygulamanın belli bir kısmını ya da tümünü yağmurdan koruyacak türde şemsiyeler yani test türleri yer alıyor. Küçük şemsiyelerin kullanımı ve taşınmaları yani test türü olarak oluşturulmaları ve koşturulmaları kolay. Bu tür testler için gerekli yatırım düşük. Buna karşın kapsama alanları kısıtlı yani küçük şemsiyelerle uygulamanın tümümü korumak mümkün değil. Bu küçük şemsiyeleri bir araya getirip, uygulamanın her tarafını koruma altına almak da mümmkün değil, çünkü yapıları itibari ile bir araya getirilip, daha büyük bir şemsiye oluşturma kabiliyetine sahip değiller. Buna karşın şemsiye modelinde tüm uygulamayı ya da büyük bölümlerini koruma altına alabilecek daha büyük şemsiyeler de bulunmakta. Bu tür şemsiyeler çok büyük bir alanı koruyabiliyorlar. Lakin oluşturulmaları ve koşturulmaları zaman alıcı. Bu yüzden maliyetleri yüksek. Şemsiye modeli ile değişik büyüklükte şemsiyeler kombine edilerek, uygulanın tümünü koruma altına almak mümkün. Şimdi bu şemsiyeleri, nasıl kullanıldıklarını, oluşturulma maliyetlerini ve uygulamayı nasıl koruklarını yakından inceleyelim.

Şemsiye modelindeki en küçük şemsiyeler birim testleridir. Birim testleri sadece bir kod birimi, bu bir metot, sınıf ya da modül olabilir, test etmek için kullanılırlar. Kod birimlerinin dışa bağımlılıkları mevcut olabilir. Birim testlerinde bu bağımlılıklar yok sayılır ve mock olarak isimlendirilen ve bağımlılıkları simüle eden kod birimleri ile yer değiştirilmeleri sağlanır. Bu şekilde sadece test edilen kod birimi bünyesinde olup, bitenleri test etmek mümkündür. Nihayi amaç kod biriminin bağımlılıkları olmadan nasıl davranış gösterdiğinin test edebilmesidir. Eğer teste test edilen kod biriminin bağımlılıkları da dahil olursa, test birim testi olmaktan çıkar ve bir entegrasyon testine dönüşür. Bu tür testlerin kullanımı da şemsiye modelinde yer almaktadır. Lakin birim testleri ile sadece işletme mantığı barındıran kod birimlerine konsantre olunur ve birim şemsiyesi açılarak belli bir kod birimi koruma altına alınır ve bağımlılıkları göz ardı edilir. Bu bağımlılıkların dolaylı olarak test edilmediği anlamına gelmektedir.


Resim 2

Resim 2 de birim şemsiyesinin işlevi görülmektedir. Bu örnekte D ismini taşıyan modül birim testleri ile koruma altına alınmıştır ve yağmur görmesi engellenmektedir. D ve diğer modüller arasında interaksiyon olduğunu düşünecek olursak, birim testleri ile bu interaksiyonu test etmediğimiz için D için açmış olduğumuz şemsiye sadece D bünyesindeki işleteme mantığını korur türdendir. Eğer interaksiyon içindeki modülleri tamamen koruma altına alacak büyüklükte bir şemsiyemiz yani testimiz yoksa, bu durumda uygulama entegrasyonun gerçekleştiği noktalarda yine yağmura mağruz kalacaktır, çünkü bu alanlar birim testleri ile test edilemez. Böyle bir şemsiyeyi bir entegrasyon testi yazarak, oluşturabiliriz. Resim 3 de bu yapıyı görmekteyiz.


Resim 3

Entegrasyon testlerinde birbirlerine bağımlı olan modüllerin davranışları test edilir. Örneğin müşteri bilgilerini edinmek için kullanılan CustomerDao ismindeki bir sınıfı test etmek için veri tabanına ihtiyaç duyulmaktadır. Birim testinde veri tabanı erişimi mock olarak implemente edilirken, entegrasyon testinde CustomerDao sınıfının erişebileceği bir veri tabanı oluşturulur ve bu iki modülün entegrasyonu test edilir.

Birim testleri ile CustomerDao sınıfının entegre edilmiş bir sistemindeki işlevini test etmek mümkün değildir. Bu belirsizlik şemsiyenin büyüklüğünü ve kapsama alanını tayin etmektedir. Bu sebepten dolayı birim testleri ile tüm uygulamayı koruma altına almak mümkün değildir, çünkü birim testleri “gerçek olmayan bir ortamda” kabiliyetleri çerçevesinde izole edilmiş kod birimleriyle ilgilidirler. Gerçek bir ortamda bir veya daha fazla modülün birlikteliğini test etmek için entegrasyon testlerine ihtiyaç duyulmaktadır. Entegrasyon testleri gerçeğe yakın bir ortam oluşturubildiklerinden, kapsama alanları daha geniştir ve böylece uygulamanın daha geniş alanlarına hitap edebilirler.

Resim 3 de entegrasyon şemsiyelerinin birim şemsiyelerine nazaran daha büyük ve ağır oldukları görülmektedir. Bu sebepten dolayı taşınmaları daha zordur. Bu entegrasyon testlerinin birim testlerine nazaran daha maliyetli oldukları ve koşturulmalarının daha zaman alıcı olduğu anlamına gelmektedir.

Birim testlerinde de olduğu gibi sadece entegrasyon testleri ile tüm uygulamayı koruma altına almak mümkün değildir, çünkü entegrasyon testleri tüm sistem entegrasyonu yerine bazı modüllerin entegrasyonuna ağırlık vermektedirler. Tüm sistem entegre edilip, test edilmediği sürece, açılan şemsiyelerin altında yer alamayan kod birimleri mutlaka olacaktır ve daha büyük şemsiyeler açılmadığı sürece, bu kod birimleri ayazda kalacaktır. Bu sistem hatalarının var olacakları anlamına gelmektedir.

Tüm uygulamayı kapsama alanına alabilecek kabiliyete sahip testler onay/kabul testleridir. Resim 4 de onay/kabul şemsiyesinin işlevi görülmektedir.


Resim 4

Onay/kabul testleri uygulamayı bir kara kutu olarak görürler ve onun kullanıcı perspektifinden test edilmesini mümkün kılarlar. Onay/kabul testleri kullanıcı ya da müşteri tarafından tanımlanan onay/kabul kriterlerini (acceptance criteria) baz alırlar ve programcılar tarafından implemente edilirler. Onay/kabul testleri ile bir uygulayı gerçek koşullarda en tepesinden en alt katmanına kadar entegre test etmek mümkündür. Topdown olarak isimlendirilen bu yaklaşım uygulamada kullanıcıların ihtiyaç duyduğu tüm uygulama davranışlarının test edilmesini mümkün kılmaktadır. Çalışır durumda olan onay/kabul testleri uygulamanın beklenen davranışı sergilediğinin kanıtıdır ve bu sebepten dolayı müşteri ya da kullanıcı tarafından uygulamanın onaylanmasını / kabul görmesini beraberinde getirir.

Bir uygulamanın çalışır durumda olduğunun en iyi ispatı onay/kabul testlerinin varlığı ve çalışır durumda olmalarıdır. Onay/kabul testleri ile uygulamanın sahip olması gereken tüm davranış biçimleri test edilebilir. Lakin resim 4 de de görüldüğü gibi onay/kabul şemsiyenin büyüklüğü maliyetine işaret etmektedir. Bu tür testlerin implementasyonları karmaşık yapılarından dolayı zaman alıcı olabilir. Bu proje maliyetini artırıcı bir durumdur. Bunun yanı sıra onay/kabul testleri tüm sistem entegrasyonunu hedef aldıklarından, koşturulma zamanları buna orantılı olarak uzundur.

Sadece onay/kabul testlerinin uygulanması bize tüm uygulamayı kapsayacak bir koruyucu şemsiyenin açılabileceği izlenimini vermekle birlikte, şemsiyenin büyüklüğünden doğan hantallık, projenin çevikliğini aksatacaktır. Buradaki yazımda da değindiğim gibi çeviklik yazılım testleri ile bir uygulamanın istenilen şekilde yoğrulması için gerekli ortamın oluşturulmasıdır. Uygulamayı yeni müşteri gereksinimleri doğrultusunda yeniden yapılandırabilmek için yapılan değişikliklerin sebep olduğu yan etkilerin lokalizasyonu için geribildirime ihtiyaç duyulmaktadır. Bu geribildirim ne kadar hızlı alınırsa, yeniden yapılandırma o oranda ivme kazanacaktır. Onay/kabul testleri bu tür geribildirim sağlama yetisine sahiplerken, koşturulma zamanları uzun olduğundan dolayı, hızlı bir şekilde geribildirim alıp, gerekli yeniden yapılandırma işlenimi seri halde gerçekleştirmek mümkün değildir. Bunun için daha hızlı geribildirim sağlayan bir mekanizmaya ihtiyac duyulmaktadır. Bu mekanizmanın ismi birim testleridir.

Görüldüğü gibi tüm sistemi koruma altına almak ve bunun yanı sıra çeviklikten ödün vermemek için değişik türdeki testlerin kombine edilmesi gerekmektedir. Bir sonraki yazımda şemsiye modelinin nasıl implemente edilebileceğini bir örnek üzerinde sizlerle paylaşacağım.


EOF (End Of Fun)
Özcan Acar

Eclipse Altında Birim ve Entegrasyon Testleri Nasıl Ayrı Koşturulur?

Çalıştığım projede aynı sınıfın entegrasyon ve birim testleri aynı paketin içinde yer alıyor. Maven ile birim testlerini yapıdan (build) önce, entegrasyon testlerini yapı ve deployment sonrası koşturmak mümkün. Bu şekilde testler ayırt edilebiliyor. Lakin Eclipse altında testleri koşturmak istediğim zaman, doğal olarak JUnit plugin testleri ayırt etmeden, beraber koşturuyor. Geliştirme esnasında bu sıkıntılı bir durum, çünkü sadece birim testlerini koşturarak, seri bir şekilde geribildirim almak istiyorum.

Bu durumu çözmek için şöyle bir TestSuite sınıfı oluşturulabilir:

[sourcecode language=’java’]
@RunWith(Suite.class)
@SuiteClasses({ MyClassTest.class, MySecondClassTest.class })
public class AllTests {

}
[/sourcecode]

AllTests sınıfı koşturulduğu taktirde, MyClassTest ve MySecondClassTest isimli testler koşturulur. Tüm birim testlerinin @SuiteClasses anotasyonunda belirtilmesi gerekiyor. Yüzlerce test sınıfının olduğu bir uygulamada birkaç birim testinin AllTests sınıfına unutularak, eklenmeyeceği aşikar. Bu yüzden bu çözümü uygun bulmuyorum.

Junit-Toolbox yardımı ile TestSuite bünyesinde koşturulacak testleri ayırt etmek mümkün. Aşağıda bunun bir örneğini görmektesiniz.

[sourcecode language=’java’]
import org.junit.runner.RunWith;

import com.googlecode.junittoolbox.ParallelSuite;
import com.googlecode.junittoolbox.SuiteClasses;

@RunWith(ParallelSuite.class)
@SuiteClasses({ “**/*Test.class”, “!**/*IntegrationTest.class”,
“!**/*RestTest.class” })
public class FastUnitTestSuite {
}
[/sourcecode]

FastUnitTestSuite bünyesinde sadece ismi Test.class ile biten testler koşturuluyor. Bunlar projede yer alan birim (unit) testleri. Entegrasyon testlerinin isimleri *IntegrationTest.class şeklinde oldugundan, !**/*IntegrationTest.class ile bu testlerin koşturulması engellenmiş oluyor.

Junit-Toolbox projesini şu şekilde Maven projesine dahil etmek mümkün.

[sourcecode language=’java’]

com.googlecode.junit-toolbox
junit-toolbox
1.8

[/sourcecode]


EOF (End Of Fun)
Özcan Acar

Test Güdümlü Yazılımın Tasarım Üzerindeki Etkileri

Yazılımcı olarak çalıştığım projelerde geleneksel ve çevik yazılım süreçleri hakkında tecrübe edinme firsatı buldum. En son kitabım bir çevik süreç olan Extreme Programming hakkındadır. Edindiğim tecrübeler doğrultusunda çevik süreçlerin, klasik yazılım süreçlerine nazaran bakımı ve geliştirilmesi daha kolay yazılım sistemlerinin oluşturulmasında daha avantajlı olduğunu söyleyebilirim.

Bu yazımda sizelere test güdümlü yazılım sürecinin, yazılım tasarımı üzerindeki etkilerini bir örnek üzerinde aktarmak istiyorum. TDD ile birlikte oluşan tasarım, kendiliğinden oluşan birşey değildir. Testler şekil aldıkça, oluşturmak istediğimiz tasarımın modeli de gözümüzde canlanmaya başlar. Oluşturduğumuz testler, programın gelecekteki kullanıcılarını (client) simule ettiği için, programın nasıl kullanılacağını testler bünyesinde gözlemlemek kolaylaşmaktadır. Bu süreç, sınıfların ve metotların kullanıcı gözüyle (client) tasarlanmasını sağlar. Bu sayede basit ve kullanışlı API (Application Programming Interface)’ler oluşur. Test güdümlü yazılım tasarımı devamlı zorlar ve yetersiz kaldığı yerlerde refactoring yöntemleriyle yenilenmesini sağlar. Bu süreç sayesinde kendisini devamlı yenileyen ve yeni gereksinimlere cevap veren bir tasarım oluşur.

Bu yazıyı PDF olarak edinebilirsiniz.


EOF (End of Fun)
Özcan Acar

Yazılımda Değişik Test Türleri

Yazılım sürecinde oluşturulan sistemin kalite kontrolü unit testleri ile yapılır. Java tabanlı sistemlerde unit testleri JUnit  olarak isimlendirilir. Bu isim aynı ismi taşıyan test frameworkü olan JUnit’den gelmektedir. Java’da unit testleri yazabilmek için JUnit frameworkü (http://www.junit.org) kullanılır.

Değişik türde unit testleri oluşturmak mümkündür. Bunlar:

  • JUnit testleri (Unit Testing)
  • Entegrasyon testleri (Integration testing)
  • Arayüz (interface) testleri (Functional Interface Testing)
  • Regresyon testleri  (Regression Testing)
  • Akseptans (onay/kabul) testleri (Acceptance Testing)
  • Sistem testleri (System Testing)
  • Stres testleri (Stress Testing)
  • Performans testleri (Performance Testing)

Programcılar sisteme eklenen her Java sınıfı için bir JUnit test sınıfı oluştururlar. Test sınıfında, test edilen sınıfın her metodu için bir test oluşturulur. Unit ismi buradan gelmektedir. Unit testleri ile  kendi içinde bütün olan bir kod ünitesi test edilir. Bunlar genellikle sınıfların metotlarıdır. Test sınıfı kullanılarak, test edilen sınıfın işlevlerini doğru olarak yerine getirip, getirmediği test edilir. Metot bazında hazırlanan testlere JUnit  testleri diyoruz. Sınıf üzerinde yapılan her değişiklik ardından o sınıf için hazırlanan unit testleri çalıştırılarak, yapılan değişikliğin yan etkileri olup, olmadığı kontrol edilir. Testlerin olumlu netice vermesi durumda, sınıfın görevini hatasız yerine getirdiği ispat edilmiş olur. Bu açıdan bakıldığında unit testleri programcılar için kodun kalitesini korumak ve daha ileri götürebilmek için vazgeçilmezdir. Sistem üzerinde yapılan her değişiklik yan etkilere sebep olabileceği için, sistemin her zaman çalışır durumda olduğunu sadece unit testleri ile kontrol edebiliriz. Onlarca ya da yüzlerce sınıfın olduğu bir programı, her değişikliğin ardından elde kontrol etmek imkansız olduğu için otomatik çalışabilen unit testlerine ihtiyacımız vardır.

Birçok komponentten oluşan bir sistemde, komponentler arası entegrasyonu test etmek için  entegrasyon testleri oluşturulur. Entegrasyon testleri hakkında detaya girmeden önce, JUnit testleri hakkında bir açıklama daha yapma gereği duyuyorum. JUnit testleri, test edilen sınıfları kullandıkları diğer sınıflardan bağımsız olarak test ederler. Örneğin bir sınıf bilgibankasından veri edinmek için bir servis sınıfını kullanıyorsa, JUnit testinde bu servis sınıfı kullanılmaz, çünkü bu bilgibankasının çalışır durumda olmasını gerektirir. Eğer JUnit testi içinde bilgibankası kullanılıyorsa, bu JUnit testi değil, entegrasyon testidir, çünkü test edilen sınıf ile bilgibankası arasındaki ilişki dolaylı olarak test edilmektedir. Daha öncede belirttiğim gibi JUnit testleri metot bazında ve sınıfın dış dünyaya olan  bağımlılıklarından bağımsız olarak gerçekleştirilir. Amacımız bir metot içinde bulunan kod satırlarının işlevini test etmektir. Bunun için bilgibankasına bağlantı oluşturulması gerekmez. Test edilen sınıfın bağımlılıklarını ortadan kaldırabilmek için Mock nesneler kullanılır. Bir Mock nesne ile servis sınıfı işlevini yerine getiriyormuşcasına simüle edilir. JUnit testinde, test edilen sınıf servis sınıfı yerine onun yerine geçmiş olan Mock nesnesini kullanarak,  işlevini yerine getirir. Entegrasyon testlerinde Mock nesneleri kullanılmaz. Entegrasyon testlerindeki ana amaç sistemin değişik bölümlerinin (subsystem) entegre edilerek işlevlerini kontrol etmektir. Entegrasyon testlerinde test edilen sınıflar için gerekli tüm altyapı (bilgibankası, email serveri vs.)  çalışır duruma getirilir ve entegrasyon test edilir.

Sistem komponentleri bir veya birden fazla sınıftan oluşabilir. Komponent kullanımını kolaylaştırmak için interface sınıflar tanımlanır. Komponenti kullanmak isteyen diğer komponent ve modüller bu interface sınıfına karşı programlanır. Komponentler arası interaksiyon Arayüz (interface) testleri ile test edilir. Bu testlere fonksiyon testleri adı da verilir.

Regresyon bir adım geri atmak anlamına gelmektir. Regresyon yazılım esnasında, programın yapılan değişiklikler sonucu çalışır durumdan, çalışmaz bir duruma geçtiği anlamına gelir. Regresyon testleri ile sistemden yapılan değişikliklerin bozulmaları neden olup, olmadığı kontrol edilir. Sistem üzerinde yapılan her değişiklik istenmeyen yan etkiler doğurabilir. Her değişikliğin ardından regresyon testleri yapılarak, sistemin bütünlüğü test edilir. Regresyon testlerinde sistem için kullanılan altyapı tanımlanmış bir duruma getirildikten sonra testler uygulanır. Örneğin test öncesi bilgibankası silinerek, test için gerekli veriler tekrar yüklenir. Her test başlangıcında aynı veriler kullanılarak, sistemin nasıl reaksiyon gösterdiği test edilir. Regresyon testlerinin uygulanabilmek için test öncesinde tüm altyapının başlangıç noktası olarak tanımlanan bir duruma getirilmesi gerekmektedir.

Akseptans testleri ile sistemin bütünü kullanıcı gözüyle test edilir. Bu tür testlerde sistem kara kutu olarak düşünülür. Bu yüzden akseptans testlerinin diğer bir ismi kara kutu testleridir (black box test).  Kullanıcının sistemin içinde ne olup bittiğine dair bir bilgisi yoktur. Onun sistemden belirli beklentileri vardır. Bu amaçla sistem ile interaksiyona girer. Akseptans testlerinde sistemden beklenen geri dönüm test edilir.

Stres testleri tüm sistemin davranışını eksepsiyonel şartlar altında test eden testlerdir. Örneğin bir web tabanlı programın eşli zamanlı yüz kullanıcı ile gösterdiği davranış, bu  rakam iki yüze çıktığında aynı olmayabilir. Stres testleri ile sistemin belirli kaynaklar ile (hardware, ram, işletim sistemi)  stres altındaki davranışı test edilmiş olur.

Performans testleri  ile sistemin, tanımlanmış kaynaklar (hardware, ram, işletim sistemi) yardımıyla beklenen performansı ölçülür. Test öncesi sistemden beklenen davranış biçimi tayin edilir. Test sonrası beklentiler  test sonuçlarıyla kıyaslanır ve kullanılan kaynakların beklenen davranış için yeterli olup olmadığı incelenir. Sistemin davranış biçimi kullanılan kaynakların yetersiz olduğunu göstermesi durumunda, ya sistem üzerinde değişikliğe gidilir ve sistemin mevcut kaynaklar ile yetinmesi sağlanır ya da kaynaklar genişletilerek sistemin istenilen davranışı edinmesi sağlanır.

Çevik süreçlerde unit testleri büyük önem taşımaktadır. Extreme Programming bir adım daha ileri giderek, test güdümlü yazılım (Test Driven Development – TDD) konseptini geliştirmiştir. Test güdümlü yazılımda baş rolü unit testleri oynar. Sınıflar oluşturulmadan önce test sınıfları oluşturulur. Bu belki ilk bakışta çok tuhaf bir yaklaşım gibi görünebilir. Var olmayan bir sınıf için nasıl unit testleri yazılabilir diye bir soru akla gelebilir. TDD uygulandığı taktirde, oluşturulan testler sadece sistemde olması gereken fonksiyonların programlanmasını sağlar. Programcılar kafalarında oluşan modelleri program koduna çevirirler. Bu süreçte çoğu zaman sistemi kullanıcı gözlüğüyle değil, programcı gözlüğüyle görürler. Bu da belki ilk etapta gerekli olmayan davranışların sisteme eklenmesine sebep verebilir. Unit testlerinde bu durum farklıdır. Örneğin akseptans testlerinde tüm sistem bir kullanıcının perspektifinden test edilir. Bu testlerde sistemin mutlaka sahip olması gerektiği davranışlar test edilmiş olur. Eğer akseptans testleri oluşturarak yazılım sürecine başlarsak, testin öngördüğü fonksiyonları implemente ederiz. Böylece gereksiz ve belki bir zaman sonra kullanılabileceğini düşünerek oluşturduğumuz fonksiyonlar programlanmaz. TDD ile testler oluşturulan sisteme paralel olarak oluşur. Sonradan bir sistem için onlarca ya da yüzlerce unit testi oluşturmak çok zor olacağı için, unit testlerini oluşturarak yazılama başlamak çok daha mantıklıdır. TDD konseptini bir sonraki bölümde detaylı olarak yakından inceleyeceğiz.

Özcan Acar

Yazılım Test Türleri

Yazılım sürecinde oluşturulan sistemin kalite kontrolü yazılım testleri ile yapılır. Bu bölümde yazılım testlerin nasıl hazırlandığını yakından inceleyeceğiz. Bir sonraki bölümünde yer alan test güdümlü yazılımı (Test Driven Development – TDD) uygulayabilmek için yazılım test konseptlerinin bilinmesi gerekmektedir. Java tabanlı sistemlerde testler JUnit çatısı kullanılarak hazırlanır.

Değişik türde yazılım testleri oluşturmak mümkündür. Bunlar:

  • Birim testleri (Unit Testing)
  • Entegrasyon testleri (Integration testing)
  • Arayüz (interface) testleri (Functional Interface Testing)
  • Regresyon testleri (Regression Testing)
  • Akseptans testleri (Acceptance Testing)
  • Sistem testleri (System Testing)
  • Stres testleri (Stress Testing)
  • Performans testleri (Performance Testing)
Bu yazıyı PDF olarak edinebilirsiniz.

  Unit Testing Konseptleri (329.4 KiB, 14,291 yükleme)

Konuyla İlgili Kitaplar