Kazanimlar:
- Birim testlerde beklenen değeri kabul kuralından bağımsız hesaplatarak yapay zekanın hatalı davranışı 'doğru' kabul etmesini önleyebilme
- AAA ve FIRST ilkelerini uygulayıp dış bağımlılıkları mock'layarak hızlı, bağımsız ve tekrarlanabilir testler yazdırabilme
- Testleri mutasyon (kodu bozma) ile sınayıp test edilmesi zor kodu bir tasarım kokusu olarak fark edebilme
Test piramidinin en geniş ve en hızlı katmanı birim testtir (unit test — bir fonksiyonu veya küçük bir kod parçasını, diğer her şeyden yalıtarak tek başına doğrulayan test). Binlerce birim test saniyeler içinde çalışır ve bir hatayı, kod daha geliştiricinin ekranındayken yakalar. Yapay zeka (YZ), birim test üretmekte belki de en yetenekli olduğu alandadır: bir fonksiyonu verirsiniz, YZ onlarca test üretir. Ama tam da bu kolaylık, en büyük tuzağı doğurur: YZ, kolayca "yeşil yanan ama hiçbir şeyi doğrulamayan" ya da kodun mevcut (belki hatalı) davranışını "doğru" kabul eden testler üretir. Bu ünitede YZ ile gerçekten koruyucu birim testler yazmayı ve test edilebilir kod ile YZ'nin ilişkisini öğreneceksiniz.
İyi birim testin nitelikleri: FIRST
İyi birim testler FIRST ilkelerine uyar: Fast (hızlı), Independent (bağımsız — testler birbirine bağlı olmamalı), Repeatable (tekrarlanabilir — her ortamda aynı sonuç), Self-validating (kendi kendini doğrulayan — geçti/kaldı net), Timely (zamanında). YZ'ye test ürettirirken bu ilkeleri hatırlatın; özellikle "bağımsız" ve "tekrarlanabilir" olması için testin dış dünyaya (gerçek veritabanı, ağ, saat) bağlı olmamasını isteyin.
AAA deseni ve anlamlı assert
Sağlam bir birim test AAA yapısını izler: Arrange (hazırla — girdileri ve bağımlılıkları kur), Act (uygula — test edilen fonksiyonu çağır), Assert (doğrula — sonucu beklenen değerle karşılaştır). Kritik olan assert'tir. YZ'nin en sık hatası, assert'i test edilen kodun çıktısından türetmesidir — yani "kod ne dönerse doğru budur" mantığı. Bu, testi anlamsız kılar. Doğrusu, beklenen değeri bağımsız olarak (kabul kriterinden, elle hesapla) belirlemektir.
Dikkat: YZ'ye "şu fonksiyona test yaz" derseniz, YZ fonksiyonu çalıştırıp çıktısını "beklenen" olarak yazabilir. Bu test, fonksiyon yanlış bile olsa geçer. Bunun yerine "beklenen sonuçları şu kurallara göre sen hesapla, fonksiyonun mevcut çıktısını referans alma" deyin.
Mock, stub ve bağımlılıklar
Birim test yalıtım ister. Fonksiyonunuz bir veritabanına veya API'ye bağlıysa, testte bunların yerine taklit nesneler (mock/stub — gerçek bağımlılığın kontrollü, sahte bir yerine geçeni) koyulur. Böylece test hızlı, bağımsız ve tekrarlanabilir olur. YZ mock kurulumunu üretebilir; ama aşırı mock'lamaya dikkat: her şeyi taklit ederseniz, test yalnızca "mock'un döndürdüğünü" doğrular, gerçek mantığı değil. Denge: dış dünyayı taklit et, test edilen mantığı gerçek çalıştır.
Test edilebilirlik ve YZ
İlginç bir geri besleme vardır: test edilmesi zor kod, çoğu zaman kötü tasarlanmış koddur. YZ bir fonksiyona test yazmakta zorlanıyorsa (çok fazla bağımlılık, gizli global durum, yan etkiler), bu bir tasarım kokusudur. YZ'ye "bu kodu test edilebilir kılmak için nasıl yeniden düzenlerdin" diye sormak, hem daha iyi test hem daha iyi kod getirir.
Parametreli testler ve veri çeşitliliği
Aynı kuralı farklı girdilerle doğrulamak için her seferinde ayrı bir test yazmak hem yorucu hem de bakımı zordur. Parametreli test (parameterized test — aynı test mantığını, bir girdi-beklenen sonuç listesi üzerinde tekrar tekrar çalıştıran yapı), bu tekrarı ortadan kaldırır: tek bir test gövdesi, onlarca girdi çiftiyle beslenir. YZ, kabul kurallarınızı verdiğinizde bu girdi-beklenen sonuç tablolarını üretmekte çok verimlidir; özellikle sınır değerleri ve denklik sınıflarını sistematik biçimde tabloya döker.
Ancak burada da tuzak vardır: YZ üretilen tabloda beklenen sonuçları yine test edilen koddan türetmeye eğilimlidir. Parametreli testte bu hata daha da tehlikelidir, çünkü tek bir yanlış mantık onlarca satırı birden geçersiz kılar. Bu yüzden beklenen sonuç sütununu her zaman kabul kuralına göre bağımsız hesaplatın ve en az birkaç satırı elle doğrulayın. Ayrıca "her satır neyi temsil ediyor" diye bir açıklama sütunu isteyin; böylece bir satır kırıldığında hangi durumun bozulduğunu anında görürsünüz.
İpucu: Parametreli test tablosuna kasıtlı olarak bir "tuzak satır" ekleyin — yani sonucu bilerek yanlış yazın. Testi çalıştırınca o satır kırmızıya dönmüyorsa, testiniz o durumu aslında doğrulamıyordur. Bu, hızlı bir sahte-geçiş kontrolüdür.
Zayıf prompt / Güçlü prompt
Zayıf: "Bu fonksiyona unit test yaz."
Güçlü: "vergiHesapla(tutar, oran) fonksiyonu için [dil/çerçeve] birim testleri yaz. Kabul kuralı: sonuç = tutar * oran, 2 ondalığa yuvarlanır; negatif tutar veya oran hata fırlatır; oran 0 ise 0 döner. AAA yapısını kullan. Beklenen değerleri BU kurallara göre sen elle hesapla; fonksiyonun mevcut çıktısını referans alma. Sınır ve negatif durumları kapsa (0, negatif, çok büyük, ondalık yuvarlama). Her testin adı doğruladığı kuralı anlatsın. Dış bağımlılık yok."
Güçlü prompt; kabul kuralını, bağımsız beklenen değer beklentisini, yapıyı ve kenar durumları verir. Böylece test kodun aynası değil, kuralın bekçisi olur.
Birim test kalite tablosu
Belirti
Kötü test (sahte-güven)
İyi test
Assert
Yok veya "null değil"
Beklenen somut değer
Beklenen değer kaynağı
Fonksiyonun çıktısı
Kabul kuralı / elle hesap
Bağımlılık
Gerçek DB/ağ/saat
Mock/stub ile yalıtılmış
Kenar durum
Yalnızca mutlu yol
Sınır, negatif, hata
Kodu bozunca
Yeşil kalır
Kırmızıya döner
İsim
test1, testMethod
doğruladığı kuralı anlatır
Dört kopyalanabilir şablon
1) Kural-güdümlü birim test:
Rolün: kıdemli yazılım test mühendisi.[Dil/çerçeve] ile şu fonksiyona birim test yaz: [imza].Kabul kuralları: [kurallar].- AAA yapısı kullan.- Beklenen değerleri BU kurallara göre elle hesapla; fonksiyonun mevcut çıktısını referans ALMA.- Sınır, negatif, hata ve mutlu yolu ayrı testlerle kapsa.- Her test adı doğruladığı kuralı anlatsın.- Dış bağımlılıkları mock'la; asıl mantığı gerçek çalıştır.
2) Mutasyon dayanıklılığı denetimi:
Bu birim testlerini incele. Test edilen kodda yapabileceğim5 küçük bozma (bir + yerine -, bir > yerine >=, bir sınırkayması) listele ve her biri için bu testlerden HANGİSİNİNkırmızıya döneceğini söyle. Hiçbiri dönmüyorsa test yetersizdir.Kod + testler: [yapıştır]
3) Test edilebilirlik incelemesi:
Bu fonksiyonu birim test yazmak neden zor? Gizli bağımlılık,global durum, yan etki, çok sorumluluk var mı? Test edilebilirkılmak için minimal yeniden düzenleme (refactor) öner;davranışı değiştirme. Kod: [yapıştır]
4) Eksik senaryo tamamlama:
Şu fonksiyon ve mevcut testler verildi. Hangi davranış/kenardurumun HİÇ test edilmediğini listele (kapsam boşluğu) ve herbiri için bir test ekle. Fonksiyon+testler: [yapıştır]
Üç mini vaka
Vaka 1 — Kodun aynası olan test. Bir geliştirici, YZ'ye yuvarlama fonksiyonu için test yazdırdı; 10 test yeşildi. Aslında fonksiyon yanlış yöne yuvarlıyordu, ama YZ beklenen değerleri fonksiyonun çıktısından almıştı, bu yüzden testler hatayı "doğru" kabul etmişti. "Kural-güdümlü" şablonla beklenen değerler elle hesaplatıldığında 4 test kırmızıya döndü ve gerçek hata ortaya çıktı.
Vaka 2 — Mutasyon denetiminin değeri. Bir ekip 45 birim testine güveniyordu. "Mutasyon dayanıklılığı denetimi" ile kodda 20 küçük bozma denendi; testler bunların yalnızca 11'ini yakaladı. Kalan 9 bozma sessizce geçti. Ekip zayıf testleri güçlendirdi; gerçek bir hesaplama hatası bir sonraki sürümde bu güçlendirilmiş testlerce yakalandı.
Vaka 3 — Test edilemezlik bir tasarım kokusu. YZ, bir sipariş fonksiyonuna test yazamıyor, sürekli gerçek veritabanına ihtiyaç duyuyordu. "Test edilebilirlik incelemesi" şablonu, fonksiyonun veritabanı erişimini içine gömdüğünü gösterdi. Bağımlılık dışarı alınınca (dependency injection) hem test yazılabildi hem kod daha temiz oldu.
Sık yapılan hatalar
- Beklenen değeri koddan türetmek. YZ'nin fonksiyon çıktısını "doğru" kabul etmesi; hatalı kodu onaylayan test.
- Assert'siz veya önemsiz assert'li test. "Hata fırlatmadı, geçti" mantığı; hiçbir şeyi doğrulamaz.
- Aşırı mock. Her şeyi taklit edip yalnızca mock'un döndürdüğünü test etmek; gerçek mantık test edilmez.
- Sadece mutlu yol. Sınır, negatif ve hata durumlarını atlamak.
- Kodu bozarak sınamamak. Mutasyon denetimi yapmadan yeşile güvenmek.
- Test edilemezliği görmezden gelmek. Zor testi zorlamak yerine kötü tasarımı fark edip düzeltmemek.
Özetle
Birim testler, test piramidinin en hızlı ve en geniş katmanıdır; hatayı en ucuz anda yakalar. YZ birim test üretmekte çok yeteneklidir ama en büyük tuzağı, beklenen değeri kodun kendisinden türeterek hatalı davranışı "doğru" kabul eden testler yazmasıdır. Çözüm: kabul kurallarını verin, beklenen değerleri elle hesaplatın, AAA ve FIRST ilkelerini uygulatın, dış dünyayı mock'layıp asıl mantığı gerçek çalıştırın ve her testi mutasyon (kodu bozma) ile sınayın. Test edilmesi zor kod, düzeltilmesi gereken bir tasarım işaretidir.
Uygulama görevi
Kendi projenizden iş kuralı içeren bir fonksiyon seçin. Kabul kurallarını yazın ve YZ'ye "kural-güdümlü birim test" şablonuyla test yazdırın; beklenen değerleri elle hesaplatın. Sonra "mutasyon dayanıklılığı denetimi"ni uygulayın: kodda en az 5 küçük bozma yapıp kaç testin kırmızıya döndüğünü ölçün. Yakalanmayan bozmalar için yeni test ekleyin. Kaç bozmanın yakalandığını (mutasyon skoru gibi) raporlayın.
Kontrol listesi
- [ ] Kabul kurallarını verip beklenen değerleri elle hesaplattım.
- [ ] Testlerin beklenen değeri koddan türetmediğinden emin oldum.
- [ ] AAA ve FIRST ilkelerine uygun, bağımsız testler kurdum.
- [ ] Dış bağımlılıkları mock'ladım, asıl mantığı gerçek çalıştırdım.
- [ ] Sınır, negatif ve hata durumlarını kapsadım.
- [ ] Kodu bozarak (mutasyon) testlerin gerçekten koruduğunu kanıtladım.