Test-Driven Development (TDD) — јесте методологија развоја у којој се тестови пишу пре имплементације кода. Програмер прво формулише очекивано понашање у виду падајућег теста, затим пише минимални код за његово пролажење и након тога рефакторише резултат. Према Martin Fowler (2023), TDD није техника тестирања — то је техника пројектовања која дисциплинује архитектуру и смањује број дефеката у фази писања кода.
Главно
Test-Driven Development — јесте пракса развоја софтвера у којој аутоматизовани тестови одређују писање продукцијског кода. За разлику од традиционалног приступа где се код прво пише, а затим тестира, TDD преокреће редослед: прво се пише тест, затим код који тај тест пролази.
Оснивачем TDD сматра се Kent Beck, који је формулисао ову праксу крајем 1990-их година у оквиру методологије Extreme Programming (XP). У књизи „Test-Driven Development: By Example" (2002) Бек је описао пет правила TDD која су постала канонска: пиши тест пре продукцијског кода, пиши тачно онолико кода колико је потребно за пролажење теста и рефакториши после сваког циклуса.
Први принцип — тест дефинише интерфејс. Програмер је приморан да размишља о томе како ће компонента бити коришћена пре него што размисли о томе како је имплементирана. Ово формира чист API од самог почетка.
Други принцип — минимална имплементација. Када је тест написан, програмер пише тачно онолико продукцијског кода колико је потребно за његово пролажење — ни ред више. Ово спречава превремену апстракцију и прекомерну сложеност коју Martin Fowler назива Speculative Generality.
Кључна разлика између TDD и тестирања „накнадно" — дисциплина редоследа. У TDD тест не само да проверава код — он усмерава његову структуру. Према истраживању Microsoft Research (Nagappan et al., 2008), тимови који примењују TDD показују смањење густине дефеката за 40–90% у поређењу са тимовима који користе традиционални приступ.
Циклус Red-Green-Refactor — је тростепени след који се понавља за сваки нови тест. Red: напиши тест који не пролази. Green: напиши минимални код да тест прође. Refactor: побољшај код без мењања његовог понашања.
Програмер пише тест који проверава још неимплементирану функционалност. У овој фази тест треба да падне — то потврђује да тест заиста нешто проверава. У окружењу Android развоја оквир JUnit 5 показује црвену индикацију за пале тестове, што је и дало име фази.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
У овој фази пише се минимални продукцијски код довољан за пролажење теста. Без икаквог вишка — само оно што је потребно за зелену индикацију. Ако имплементација може бити константа — нека буде константа. Рефакторинг ће се десити у следећем кораку када се појаве нови тестови.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
Зелени тест је осигурање за рефакторинг. Програмер може да препише имплементацију, оптимизује перформансе или побољша читљивост, сигуран да ће тест одмах открити било какво одступање од очекиваног понашања. У мобилном развоју на Android-у ова фаза је посебно важна за издвајање заједничких интерфејса и смањење дуплирања кода.
Примена TDD у мобилним пројектима даје мерљиве предности, потврђене како академским истраживањима, тако и праксом водећих развојних студија.
Истраживање IBM (Bhat & Nagappan, 2006) на четири индустријска пројекта показало је да тимови који користе TDD праве 40% мање дефеката у поређењу са сличним тимовима који раде по традиционалној шеми. За мобилни развој, где је цена исправке бага после објављивања у Google Play значајно већа него у фази писања кода, ова метрика је критична.
Тестови написани по TDD служе као жива документација API-ја. Програмер који долази у пројекат може да чита тестове и разуме како треба да се користи свака компонента. Ово је посебно вредно у условима високе флуктуације тима — типичног проблема мобилних студија.
Покривеност кода тестовима која прелази 90% омогућава програмерима да спроводе рефакторинг без страха да ће нешто покварити. Google у својој књизи „Software Engineering at Google" (2020) назива тест покривеност кључним фактором који омогућава одржавање базе кода чистом у пројектима са милионима линија кода.
Екосистем TDD у мобилном развоју укључује алате за јединичне тестове, mocking и проверу UI компоненти — како за Android, тако и за iOS.
| Алат | Платформа | Намена |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | Основни оквир за јединичне тестове |
| Mockito | Android | Креирање mock објеката и верификација позива |
| MockK | Android (Kotlin) | Mocking са Kotlin-first синтаксом и подршком за корутине |
| Turbine | Android | Тестирање Kotlin Flow и реактивних токова |
| XCTest | iOS (Swift) | Стандардни оквир за тестирање |
За Android пројекте на Kotlin стандардни стек укључује JUnit 5 + MockK. MockK је пожељнији од Mockito-а јер подржава Kotlin функције прве класе — sealed class, корутине и suspend-функције без додатног подешавања.
У iOS развоју TDD се имплементира кроз XCTest — уграђени Apple оквир који пружа асерције, тест класе и интеграцију са CI/CD путем Xcode Server или GitHub Actions. За mocking на iOS-у користе се библиотеке Cuckoo и OHHTTPStubs.
Размотримо реални сценарио TDD у Kotlin за Android — тестирање репозиторијума корисника. Прво пишемо тест, затим — имплементацију која пролази тај тест.
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
}
}
Након пролажења првог теста додајемо други — проверавамо понашање при грешци мреже. Сада тест одређује да при паду API-ја репозиторијум треба да врати податке из кеша.
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 праћен је типичним грешкама које могу поништити све предности методологије. Разумевање ових замки помаже тимовима да ефикасније имплементирају праксу.
Први и најраспрострањенији анти-образац — тестирање превеликог обима функционалности у једном тесту. Тест треба да проверава тачно једну тврдњу (један асерт). Ако тест падне, програмер треба да зна шта се тачно покварило без додатног отклањања грешака.
Друга грешка — писање теста који одмах пролази. Ако тест није био црвен бар једном, нема сигурности да уопште нешто проверава. Правило: никада не веруј тесту који ниси видео да пада.
Трећа типична грешка — заустављање на зеленој фази. Рефакторинг није опциона, већ обавезна фаза циклуса. Без ње база кода деградира, тестови постају крхки и предности TDD се губе.
Често постављана питања
TDD је пре свега техника пројектовања, а не тестирања. Тестови у TDD играју улогу спецификације: они дефинишу API компоненте пре њене имплементације. Сам Kent Beck назива TDD „дисциплином пројектовања, а не тестирања".
Према истраживањима Microsoft Research, тимовима је потребно од 3 до 6 месеци непрекидне праксе да TDD уђе у навику. Прве 2–3 недеље продуктивност пада за 15–30%, али након адаптације враћа се на почетни ниво или га премашује захваљујући смањењу времена за отклањање грешака.
Да, али са ограничењима. За UI логику (ViewModel, State) TDD се примењује директно. За визуелне компоненте (Compose UI, SwiftUI Views) тестирање снимцима екрана (snapshot testing) допуњује TDD, али га не замењује. Препоручује се раздвајање пословне логике и приказа.
За legacy код препоручује се стратегија „карактеризационих тестова" (characterization tests) — када се тестови пишу на постојеће понашање, а затим се код рефакторише. Овај приступ описан у књизи Michael Feathers „Working Effectively with Legacy Code" (2004) омогућава постепено увођење TDD.
TDD и Clean Architecture се међусобно појачавају. Чиста архитектура захтева јасне границе између слојева, а TDD приморава програмера да пројектује те границе кроз тестове. Доменски слој се тестира изоловано са mock зависностима, слој података — кроз интеграционе тестове.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође