TDD — Red, Green, Refactor
Önce şunu oku: Test Piramidi, JUnit 5 ve Mockito
30 saniyede özet
TDD'de testi koddan önce yazarsın ve önce kırmızı yandığını görürsün. Neden? Hiç kırmızı yanmamış bir test, gerçekten bir şeyi kontrol ettiğini hiç kanıtlamamıştır.
Bazı yazılımcıların tuhaf bir alışkanlığı var: kodu yazmadan önce onu sınayacak testi yazarlar. Sebebini bilmezsen bu bir ritüele dönüşür, o yüzden önce sebebe bakalım.
-
Bayt: Kodu yazdım, ardından testini yazdım. İlk seferde yeşil yandı!
-
Sen: Harika, demek ki kod doğru.
-
Bayt: Ya da test hiçbir şeyi kontrol etmiyor. İkisini nasıl ayırt ederim?
-
Bayt: Testi bir kez kırmızı görseydim bilirdim...
Adım adım oku
- Önce test yazılır; kod henüz olmadığı için test düşer. Bu kırmızı, testin gerçekten bir şeyi kontrol ettiğinin kanıtıdır.
- Sonra testi geçirecek en basit kod yazılır ve test yeşile döner.
- Testler yeşilken kod düzenlenir; davranışın bozulmadığını testler söyler.
- Kod önce yazılırsa test ilk anda yeşildir ve hiç kırmızı görmediği için hiçbir şey kanıtlamaz.
Kırmızı adım neyi kanıtlar?
Yeni yazdığın bir test daha ilk çalıştırmada yeşil geçti. Bu, kodun doğru olduğunu gösterir mi? Cevabı göster
Hiçbir şey göstermez. Test kod doğru olduğu için de geçiyor olabilir, hiçbir şeyi kontrol etmediği için de; assertTrue(true) de geçer.
Testi önce kırmızı görmek, aradaki farkı gösteren tek kanıttır. Kırmızıdan yeşile geçişi izlediğinde, o testin gerçekten o davranışa bağlı olduğunu bilirsin.
Kafam karıştı, daha basit anlat
Hiç kırmızı yanmamış bir test, alarmı hiç çalmamış bir yangın dedektörü gibidir. Gerçekten çalıştığını bilmezsin. Kırmızı adım, dedektörün duman görünce öttüğünü gösterir.
TDD'nin üç adımı nedir?
Kendin gör
Aşağıdaki kata kendi kendine doğru döngüyü işletiyor. Önce temiz bir tur izle, sonra kırmızı düğmelerle kısayolları dene ve Suite güveni çubuğuna ne olduğuna bak.
TDD döngüsü — Red, Green, Refactor
Tohum 2Test suite
- fizzBuzz(1) == "1"
- fizzBuzz(3) == "Fizz"
- fizzBuzz(6) == "Fizz"
- fizzBuzz(5) == "Buzz"
- fizzBuzz(15) == "FizzBuzz"
Üretim kodu
// henüz kod yokYazılmış testlerin kaçı kırmızı görülerek kanıtlandı.
Şu an ne oldu?
RED — sıradaki: fizzBuzz(1) == "1"
Önce başarısız olan bir test yazılır. Testi kırmızı görmek, o testin gerçekten bir şeyi kontrol ettiğinin tek kanıtıdır.
Aklında kalsın: "Neden önce test?" sorusunun cevabı tasarım değil, kanıttır: kırmızı görmediğin bir test, sessizce hep yeşil kalabilir.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
Üç kısayol, üç farklı kayıp:
Önce kodu yaz— test yazıldığı anda yeşil. Suite dolu görünüyor ama o test hiçbir zaman bir hatayı yakalayacağını kanıtlamadı. En sinsi olanı bu: hiçbir alarm çalmaz.Hepsini bir anda yaz— kalan bütün testler yazıldıkları anda yeşil oluyor. Suite güveni çöküyor. Coverage aracı bunu %100 gösterir.Kırmızıyken refactor et— davranışı değiştirmediğini doğrulayacak hiçbir şey yok, çünkü suite zaten kırmızı.
Bir de üçgenleme adımına dikkat et: fizzBuzz(6) == "Fizz" testi yazıldığı anda geçiyor ve bu beklenen davranış. Simülatör bunu ihlal olarak saymıyor, çünkü amacı yeni davranış sürüklemek değil, mevcut genellemeyi doğrulamak.
Testi önce **kırmızı** görmek neden önemli?
Üç adımın her birinin işi ayrı
| Adım | Amaç | Atlanırsa |
|---|---|---|
| Red | Testin gerçekten bir şeyi kontrol ettiğini kanıtlamak | Sessizce hep yeşil kalan testler |
| Green | Testi geçirecek en az kodu yazmak | Hiçbir testin sürüklemediği, gereksiz kod |
| Refactor | Davranış sabitken yapıyı iyileştirmek | TDD “önce test yaz”a iner, tasarım kazancı kaybolur |
En çok atlanan adım refactor. Atlanınca geriye kalan şey test-first geliştirmedir; işe yarar ama TDD’nin tasarım faydasının tamamı o üçüncü adımdadır.
Green adımında “aptal” kod yazmak normaldir.
String fizzBuzz(int n) { return "1"; // evet, gerçekten böyle yazılır}Bu kasıtlıdır. Genelleme, bir sonraki kırmızı testin zorlamasıyla gelir:
assertEquals("2", fizzBuzz(2)); // "1" döndüren kod artık yetmiyor
String fizzBuzz(int n) { return String.valueOf(n); // genelleme, tahminle değil baskıyla geldi}Buna üçgenleme (triangulation) denir. Fazladan yazdığın her satır, hiçbir testin talep etmediği koddur — ve hiçbir testin talep etmediği kod, hiçbir testin korumadığı koddur.
Aynı döngüyü bir bankanın havale ekranındaki IBAN kontrolünde izleyebilirsin. Testler yazıldıkları sırayla duruyor; sonuncusu bir müşterinin bildiriminden doğdu.
Derinleş · IBAN kontrolü: test test büyüyen kod 3 dosya · ~57 satır · ilk okumada atlayabilirsin
Kafam karıştı, daha basit anlat
Kırmızı: test başarısız olur. Yeşil: en basit kodla testi geçirirsin. Toplama: kodu düzenlersin ve testler yeşil kalır; bu adım atlanırsa dağınıklık birikir.
Her eylemi TDD döngüsünde ait olduğu adıma yerleştir.
Her ifadeyi, TDD'nin gerçekten verdiği bir şey mi yoksa ona yüklenen bir beklenti mi olduğuna göre ayır.
Coverage yalan söyler, mutasyon testi söylemez
Test coverageTestler sırasında çalıştırılan kod oranı. Kodun doğrulandığını değil, yalnızca çalıştırıldığını gösterir — assertion içermeyen bir test de coverage üretir.Sözlükte gör → yalnızca “bu satır çalıştı mı” sorusunu ölçer. Şu test %100 coverage üretir ve hiçbir şey doğrulamaz:
@Testvoid fizzBuzzCalisir() { fizzBuzz(15); // assertion yok — satır çalıştı, coverage sayıldı}Mutasyon testi doğru soruyu sorar: kodu kasten bozarsam testler bunu yakalar mı? PIT gibi araçlar > işaretini >= yapar, bir return değerini değiştirir, bir if’i ters çevirir — ve hiçbir testin kırmızıya dönmediği her mutasyon testiKodu kasten bozup testlerin fark edip etmediğini ölçer. Coverage'ın cevaplayamadığı asıl soruyu sorar: bu testler gerçekten bir şey koruyor mu?Sözlükte gör →u rapor eder.
mvn org.pitest:pitest-maven:mutationCoverage# Çıktı: mutation score — coverage'dan çok daha dürüst bir sayıSimülatördeki suite güveni çubuğu kabaca bu fikri gösterir: kaç test gerçekten bir şeyi kanıtladı.
Kafam karıştı, daha basit anlat
Coverage yalnızca “bu satır çalıştı mı” diye sorar, “sonuç doğru mu” diye sormaz. Mutasyon testi kodu bilerek bozar ve testlerin bunu yakalayıp yakalamadığına bakar.
TDD ile yazılmış bir kodu refactor ettin ve 20 test kırıldı. Bu ne anlama gelir?
Tuzaklar: TDD her yerde gerekmez
Sınırlarını bilmek, yöntemi savunmaktan daha değerli:
- İyi uyduğu yerler: net girdi/çıktı olan iş kuralları, hata düzeltme (önce hatayı gösteren kırmızı test), algoritmalar, refactor güvenlik ağı.
- Kötü uyduğu yerler: henüz nasıl görüneceğini bilmediğin keşifsel UI çalışması, atılacak prototipler, ağır dış bağımlılık içeren entegrasyon noktaları.
Bir hata bildirimi geldiğinde ise TDD neredeyse her zaman doğru araçtır: önce hatayı yeniden üreten kırmızı testi yaz. Böylece hem düzelttiğini kanıtlamış hem de aynı hatanın geri gelmesini engellemiş olursun.
Kendini sına
İlk yazıldığı anda geçen bir test, kodun doğru olduğunu kanıtlar.
TDD'de testi önce kırmızı görmenin asıl sebebi nedir?
Aklında kalacak üç şey
- 1 Testi bir kez kırmızı görmek, onun gerçekten bir şeyi kontrol ettiğinin tek kanıtıdır. Tasarıma faydası ikincildir.
- 2 Refactor testler yeşilken yapılır: davranışın değişmediğini söyleyen tek şey geçen testlerdir.
- 3 Coverage bunu ölçmez: %100 coverage, hiç kırmızı görülmemiş testlerle de elde edilir. Farkı mutasyon testi (PIT) ölçer.
5 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.