Test-Driven Development (TDD) — kodun icrasından əvvəl testlərin yazıldığı inkişaf metodologiyasıdır. Proqramçı əvvəlcə gözlənilən davranışı uğursuz test şəklində formullaşdırır, sonra onu keçmək üçün minimal kod yazır və bundan sonra nəticəni refaktor edir. Martin Fowler (2023)-ə görə, TDD test texnikası deyil — bu, arxitekturanı nizamlayan və kod yazma mərhələsində qüsurların sayını azaldan dizayn texnikasıdır.
Əsas
Test-Driven Development — avtomatlaşdırılmış testlərin istehsal kodunun yazılmasını müəyyən etdiyi proqram təminatı inkişaf etdirmə təcrübəsidir. Ənənəvi yanaşmadan fərqli olaraq, kod əvvəlcə yazılır, sonra test edilir, TDD ardıcıllığı tərsinə çevirir: əvvəlcə test yazılır, sonra bu testi keçən kod.
TDD-nin banisi Kent Beck hesab olunur, o, bu təcrübəni 1990-cı illərin sonunda Extreme Programming (XP) metodologiyası çərçivəsində formullaşdırmışdır. „Test-Driven Development: By Example" (2002) kitabında Bek TDD-nin beş qaydasını təsvir etmişdir: istehsal kodundan əvvəl test yaz, testi keçmək üçün lazım olan qədər kod yaz və hər dövrdən sonra refaktor et.
Birinci prinsip — test interfeysi müəyyən edir. Proqramçı komponentin necə icra edildiyini düşünməzdən əvvəl onun necə istifadə ediləcəyini düşünməyə məcbur olur. Bu, ən əvvəldən təmiz API formalaşdırır.
İkinci prinsip — minimal icra. Test yazıldıqda, proqramçı onu keçmək üçün lazım olan qədər istehsal kodu yazır — bir sətir də artıq deyil. Bu, Martin Fowlerin Speculative Generality adlandırdığı vaxtından əvvəl abstraksiyanın və həddindən artıq mürəkkəbliyin qarşısını alır.
TDD ilə „fakt sonra" test arasındakı əsas fərq — ardıcıllıq intizamıdır. TDD-də test təkcə kodu yoxlamır — onun strukturunu istiqamətləndirir. Microsoft Research (Nagappan et al., 2008) tədqiqatına görə, TDD tətbiq edən komandalar ənənəvi yanaşma istifadə edən komandalarla müqayisədə qüsur sıxlığını 40–90% azaldır.
Red-Green-Refactor dövrü — hər yeni test üçün təkrarlanan üç addımlı ardıcıllıqdır. Red: keçməyən test yaz. Green: testi keçmək üçün minimal kod yaz. Refactor: davranışını dəyişmədən kodu təkmilləşdir.
Proqramçı hələ icra edilməmiş funksionallığı yoxlayan test yazır. Bu mərhələdə test uğursuz olmalıdır — bu, testin həqiqətən nəyisə yoxladığını təsdiqləyir. Android inkişaf mühitində JUnit 5 freymvorku uğursuz testlər üçün qırmızı göstərici göstərir, bu da mərhələyə adını vermişdir.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
Bu mərhələdə testi keçmək üçün kifayət edən minimal istehsal kodu yazılır. Heç bir artıqlıq — yalnız yaşıl göstərici üçün lazım olan. İcra sabit ola bilərsə — qoy sabit olsun. Refaktorinq yeni testlər görünəndə növbəti addımda baş verəcək.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
Yaşıl test refaktorinq üçün sığortadır. Proqramçı icranı yenidən yaza, performansı optimallaşdıra və ya oxunaqlılığı yaxşılaşdıra bilər, əmin olaraq ki, test gözlənilən davranışdan hər hansı bir kənarlaşmanı dərhal aşkarlayacaq. Android-də mobil inkişafda bu mərhələ ümumi interfeysləri ayırmaq və kod təkrarlanmasını azaltmaq üçün xüsusilə vacibdir.
TDD-nin mobil layihələrdə tətbiqi həm akademik tədqiqatlarla, həm də aparıcı inkişaf studiyalarının təcrübəsi ilə təsdiqlənmiş ölçülə bilən üstünlüklər verir.
IBM (Bhat & Nagappan, 2006) tərəfindən dörd sənaye layihəsində aparılan tədqiqat göstərdi ki, TDD istifadə edən komandalar ənənəvi sxem üzrə işləyən oxşar komandalarla müqayisədə 40% az qüsur buraxır. Google Play-də buraxılışdan sonra səhvin düzəltmə dəyərinin kod yazma mərhələsindən əhəmiyyətli dərəcədə yüksək olduğu mobil inkişaf üçün bu metrik kritikdir.
TDD-ə uyğun yazılmış testlər API-nin canlı sənədləşməsi kimi xidmət edir. Layihəyə gələn proqramçı testləri oxuya və hər bir komponentin necə istifadə edilməli olduğunu başa düşə bilər. Bu, mobil studiyaların tipik problemi olan yüksək kadr dəyişkənliyi şəraitində xüsusilə dəyərlidir.
90%-dən çox kod əhatəsi proqramçılara nəyisə pozmaq qorxusu olmadan refaktorinq aparmağa imkan verir. Google „Software Engineering at Google" (2020) kitabında test əhatəsini milyonlarla sətir kod olan layihələrdə kod bazasını təmiz saxlamağa imkan verən əsas amil adlandırır.
Mobil inkişafda TDD ekosisteminə vahid testlər, mocking və UI komponentlərinin yoxlanılması üçün alətlər daxildir — həm Android, həm də iOS üçün.
| Alət | Platforma | Təyinat |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | Vahid testlər üçün əsas freymvork |
| Mockito | Android | Mock-obyektlərin yaradılması və çağırışların yoxlanılması |
| MockK | Android (Kotlin) | Kotlin-first sintaksisi və korutin dəstəyi ilə mocking |
| Turbine | Android | Kotlin Flow və reaktiv axınların test edilməsi |
| XCTest | iOS (Swift) | Standart test freymvorku |
Kotlin-də Android layihələri üçün standart yığma JUnit 5 + MockK daxildir. MockK Mockito-dan üstün tutulur, çünki əlavə konfiqurasiya olmadan Kotlin birinci dərəcəli funksiyalarını — sealed class, korutinlər və suspend-funksiyaları dəstəkləyir.
iOS inkişafında TDD XCTest vasitəsilə icra olunur — Apple-ın daxili freymvorku, asertlər, test sinifləri və CI/CD ilə Xcode Server və ya GitHub Actions vasitəsilə inteqrasiya təmin edir. iOS-da mocking üçün Cuckoo və OHHTTPStubs kitabxanaları istifadə olunur.
Android üçün Kotlin dilində real TDD ssenarisini nəzərdən keçirək — istifadəçi repozitoriyasının test edilməsi. Əvvəlcə test yazırıq, sonra — bu testi keçən icra.
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
}
}
Birinci testi keçdikdən sonra ikincini əlavə edirik — şəbəkə xətası zamanı davranışı yoxlayırıq. İndi test müəyyən edir ki, API uğursuz olduqda repozitoriya keşdən məlumatları qaytarmalıdır.
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-yə keçid metodologiyanın bütün üstünlüklərini heçə endirə bilən tipik səhvlərlə müşayiət olunur. Bu tələləri başa düşmək komandalara təcrübəni daha səmərəli tətbiq etməyə kömək edir.
Birinci və ən geniş yayılmış anti-nümunə — bir testdə çox böyük həcmdə funksionallığın test edilməsi. Test tam olaraq bir ifadəni (bir asert) yoxlamalıdır. Test uğursuz olarsa, proqramçı əlavə debug etmədən nəyin pozulduğunu bilməlidir.
İkinci səhv — əvvəldən keçən testin yazılması. Test heç olmasa bir dəfə qırmızı olmayıbsa, onun ümumiyyətlə nəyisə yoxladığına əminlik yoxdur. Qayda: uğursuz olduğunu görmədiyin teste heç vaxt güvənmə.
Üçüncü tipik səhv — yaşıl mərhələdə dayanmaqdır. Refaktorinq dövrün isteğe bağlı deyil, məcburi mərhələsidir. Onsuz kod bazası deqradasiyaya uğrayır, testlər kövrək olur və TDD-nin üstünlükləri itir.
Tez-tez verilən suallar
TDD ilk növbədə dizayn texnikasıdır, test texnikası deyil. TDD-də testlər spesifikasiya rolunu oynayır: onlar komponentin API-ni onun icrasından əvvəl müəyyən edir. Kent Beck-in özü TDD-ni „dizayn intizamı, test intizamı deyil" adlandırır.
Microsoft Research tədqiqatlarına görə, TDD-nin vərdiş halına gəlməsi üçün komandalara 3 aydan 6 aya qədər fasiləsiz təcrübə lazımdır. İlk 2–3 həftə məhsuldarlıq 15–30% düşür, lakin adaptasiyadan sonra debug vaxtının qısalması hesabına ilkin səviyyəyə qayıdır və ya onu üstələyir.
Bəli, lakin məhdudiyyətlərlə. UI məntiqi (ViewModel, State) üçün TDD birbaşa tətbiq olunur. Vizual komponentlər (Compose UI, SwiftUI Views) üçün ekran görüntüsü testi (snapshot testing) TDD-ni tamamlayır, lakin onu əvəz etmir. Biznes məntiqini və təqdimatı ayırmaq tövsiyə olunur.
Legacy kod üçün „xarakterizasiya testləri" (characterization tests) strategiyası tövsiyə olunur — testlər mövcud davranışa yazılır, sonra kod refaktor edilir. Michael Feathers-in „Working Effectively with Legacy Code" (2004) kitabında təsvir edilən bu yanaşma TDD-ni mərhələli şəkildə tətbiq etməyə imkan verir.
TDD və Clean Architecture bir-birini gücləndirir. Təmiz arxitektura təbəqələr arasında aydın sərhədlər tələb edir, TDD isə proqramçını bu sərhədləri testlər vasitəsilə dizayn etməyə məcbur edir. Domen təbəqəsi mock-asılılıqlarla izolyasiya olunmuş şəkildə test edilir, məlumat təbəqəsi — inteqrasiya testləri vasitəsilə.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun