Test-Driven Development (TDD), kod uygulamasından önce testlerin yazıldığı bir geliştirme metodolojisidir. Geliştirici önce beklenen davranışı başarısız bir test olarak formüle eder, ardından onu geçmek için minimum kodu yazar ve sonucu yeniden düzenler. Martin Fowler (2023)'a göre, TDD bir test tekniği değildir — mimariyi disipline eden ve kod yazma aşamasındaki kusur sayısını azaltan bir tasarım tekniğidir.
Önemli Noktalar
Test-Driven Development, otomatik testlerin üretim kodunun yazılmasını belirlediği bir yazılım geliştirme pratiğidir. Kodun yazılıp ardından test edildiği geleneksel yaklaşımın aksine, TDD sırayı tersine çevirir: önce test yazılır, sonra testi geçen kod yazılır.
TDD'nin kurucusu, bu pratiği 1990'ların sonunda Extreme Programming (XP) metodolojisinin bir parçası olarak formüle eden Kent Beck olarak kabul edilir. “Test-Driven Development: By Example” (2002) kitabında Beck, kanonik hale gelen beş TDD kuralını tanımladı: testi üretim kodundan önce yaz, testi geçmek için yeterli kadar kod yaz ve her döngüden sonra yeniden düzenle.
İlk ilke — test arayüzü tanımlar. Geliştirici, bir bileşenin nasıl uygulanacağını düşünmeden önce nasıl kullanılacağını düşünmek zorunda kalır. Bu, en başından itibaren temiz bir API oluşturur.
İkinci ilke — minimum uygulama. Test yazıldığında, geliştirici testi geçmek için gereken kadar üretim kodu yazar — bir satır fazla değil. Bu, Martin Fowler'ın Speculative Generality olarak adlandırdığı erken soyutlamayı ve aşırı karmaşıklığı önler.
TDD ile sonradan test etme arasındaki temel fark — sıra disiplinidir. TDD'de test yalnızca kodu doğrulamaz — kodun yapısını yönlendirir. Microsoft Research araştırmasına (Nagappan et al., 2008) göre, TDD uygulayan ekipler, geleneksel yaklaşımı kullanan ekiplere kıyasla kusur yoğunluğunda %40–90 oranında azalma gösterir.
Red-Green-Refactor döngüsü, her yeni test için tekrarlanan üç adımlı bir dizidir. Red: Geçmeyen bir test yaz. Green: Testi geçmek için minimum kodu yaz. Refactor: Davranışını değiştirmeden kodu iyileştir.
Geliştirici, henüz uygulanmamış bir işlevselliği doğrulayan bir test yazar. Bu aşamada test başarısız olmalıdır — bu, testin gerçekten bir şeyi doğruladığını onaylar. Android geliştirme ortamında, JUnit 5 çerçevesi başarısız testler için kırmızı bir gösterge gösterir ve bu aşamaya adını vermiştir.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
Bu aşamada, testi geçmek için yeterli minimum üretim kodu yazılır. Fazlalık yok — yalnızca yeşil gösterge için gereken kadar. Uygulama bir sabit olabiliyorsa, sabit olsun. Yeniden düzenleme, yeni testler ortaya çıktığında bir sonraki adımda gerçekleşecektir.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
Yeşil test, yeniden düzenleme için bir sigortadır. Geliştirici, testin beklenen davranıştan herhangi bir sapmayı anında tespit edeceğine güvenerek uygulamayı yeniden yazabilir, performansı optimize edebilir veya okunabilirliği iyileştirebilir. Android mobil geliştirmede bu aşama, ortak arayüzleri çıkarmak ve kod tekrarını azaltmak için özellikle önemlidir.
Mobil projelerde TDD uygulaması, hem akademik araştırmalar hem de önde gelen geliştirme stüdyolarının pratiği tarafından onaylanan ölçülebilir faydalar sağlar.
Dört endüstriyel proje üzerinde yapılan IBM araştırması (Bhat & Nagappan, 2006), TDD kullanan ekiplerin geleneksel şemayla çalışan benzer ekiplere kıyasla %40 daha az kusur ürettiğini göstermiştir. Google Play'de yayınlandıktan sonra bir hatayı düzeltme maliyetinin kod yazma aşamasından önemli ölçüde daha yüksek olduğu mobil geliştirme için bu metrik kritiktir.
TDD ile yazılan testler, API'nin canlı dokümantasyonu olarak hizmet eder. Projeye katılan bir geliştirici, testleri okuyarak her bir bileşenin nasıl kullanılması gerektiğini anlayabilir. Bu, özellikle yüksek ekip devir hızı koşullarında — mobil stüdyoların tipik bir sorunu — değerlidir.
%90'ı aşan kod kapsamı, geliştiricilerin bir şeyi kırma korkusu olmadan yeniden düzenleme yapmasına olanak tanır. Google, “Software Engineering at Google” (2020) kitabında test kapsamını, milyonlarca satır kodlu projelerde kod tabanını temiz tutmayı sağlayan kilit faktör olarak adlandırır.
Mobil geliştirmede TDD ekosistemi, hem Android hem de iOS için birim test, mocking ve kullanıcı arayüzü bileşen doğrulama araçlarını içerir.
| Araç | Platform | Amaç |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | Birim testler için temel çerçeve |
| Mockito | Android | Mock nesneleri oluşturma ve çağrıları doğrulama |
| MockK | Android (Kotlin) | Kotlin-first sözdizimi ve coroutine desteğiyle mocking |
| Turbine | Android | Kotlin Flow ve reaktif akışları test etme |
| XCTest | iOS (Swift) | Standart test çerçevesi |
Kotlin ile Android projeleri için standart yığın JUnit 5 + MockK içerir. MockK, ek yapılandırma gerektirmeden Kotlin'in birinci sınıf özelliklerini (sealed class, coroutine ve suspend işlevleri) desteklediği için Mockito'ya tercih edilir.
iOS geliştirmede TDD, XCTest aracılığıyla uygulanır — Apple'ın iddialar (assertions), test sınıfları ve Xcode Server veya GitHub Actions aracılığıyla CI/CD entegrasyonu sağlayan yerleşik çerçevesi. iOS'te mocking için Cuckoo ve OHHTTPStubs kütüphaneleri kullanılır.
Android için Kotlin'de gerçek bir TDD senaryosunu ele alalım — bir kullanıcı deposunun test edilmesi. Önce testi yazıyoruz, ardından testi geçen uygulamayı yazıyoruz.
class UserRepositoryTest {
private val api = mockk<UserApi>()
private val dao = mockk<UserDao>()
private val repo = UserRepository(api, dao)
fun `when api returns user then cache and emit`() = runTest {
val user = User(1, "Alice")
coEvery { api.getUser(1) } returns user
every { dao.insert(user) } returns Unit
val result = repo.getUser(1)
assertEquals(user, result)
verify { dao.insert(user) }
}
}
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUser(id: Int): User {
val user = api.getUser(id)
dao.insert(user)
return user
}
}
İlk testi geçtikten sonra ikinci bir test ekliyoruz — ağ hatası sırasındaki davranışı doğruluyoruz. Şimdi test, API başarısız olduğunda deponun önbellekten veri döndürmesi gerektiğini belirler.
fun `when api fails then return cached user`() = runTest {
val cached = User(1, "Cached Alice")
coEvery { api.getUser(1) } throws IOException()
every { dao.getById(1) } returns cached
val result = repo.getUser(1)
assertEquals(cached, result)
}
TDD'ye geçiş, metodolojinin tüm avantajlarını geçersiz kılabilecek tipik hatalarla birlikte gelir. Bu tuzakları anlamak, ekiplerin pratiği daha etkili bir şekilde benimsemesine yardımcı olur.
İlk ve en yaygın anti-desen — tek bir testte çok fazla işlevselliği test etmek. Bir test tam olarak tek bir iddiayı doğrulamalıdır. Bir test başarısız olursa, geliştirici ek hata ayıklama olmadan tam olarak neyin bozulduğunu bilmelidir.
İkinci hata — en başından itibaren geçen bir test yazmak. Test en az bir kez kırmızı olmadıysa, gerçekten bir şeyi doğruladığına dair güven yoktur. Kural: başarısız olduğunu görmediğiniz bir teste asla güvenmeyin.
Üçüncü yaygın hata — yeşil aşamada durmak. Yeniden düzenleme isteğe bağlı değil, döngünün zorunlu bir adımıdır. O olmadan kod tabanı bozulur, testler kırılgan hale gelir ve TDD'nin faydaları kaybolur.
Sıkça Sorulan Sorular
TDD her şeyden önce bir tasarım tekniğidir, bir test tekniği değil. TDD'deki testler bir spesifikasyon rolü oynar: bileşenin API'sini uygulamasından önce tanımlarlar. Kent Beck'in kendisi TDD'yi “bir test disiplini değil, bir tasarım disiplini” olarak adlandırır.
Microsoft Research araştırmalarına göre, TDD'nin alışkanlık haline gelmesi için ekiplerin 3 ila 6 ay sürekli pratik yapması gerekir. İlk 2–3 haftada üretkenlik %15–30 oranında düşer, ancak adaptasyondan sonra hata ayıklama süresindeki azalma sayesinde orijinal seviyeye döner veya onu aşar.
Evet, ancak sınırlamalarla. Kullanıcı arayüzü mantığı (ViewModel, State) için TDD doğrudan uygulanabilir. Görsel bileşenler (Compose UI, SwiftUI Views) için anlık görüntü testi (snapshot testing) TDD'yi tamamlar ancak yerini almaz. İş mantığı ve görüntülemenin ayrılması önerilir.
Eski kod için, “karakterizasyon testleri” stratejisi önerilir — mevcut davranış üzerine testler yazılır ve ardından kod yeniden düzenlenir. Bu yaklaşım Michael Feathers'in “Working Effectively with Legacy Code” (2004) kitabında açıklanmıştır ve TDD'nin aşamalı olarak uygulanmasına olanak tanır.
TDD ve Clean Architecture birbirini güçlendirir. Temiz mimari, katmanlar arasında net sınırlar gerektirir ve TDD, geliştiriciyi bu sınırları testler aracılığıyla tasarlamaya zorlar. Domain katmanı, mock bağımlılıklarıyla izole bir şekilde test edilir, veri katmanı ise entegrasyon testleri aracılığıyla test edilir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun