Unit тестирање је метод провере софтвера при којем се тестира исправност појединачних модула или функција кода у изолацији од остатка система. Према Martin Fowler, 2026, unit тестови су темељ CI/CD и рефакторисања, обезбеђујући брзу повратну информацију о исправности кода. Модуларно тестирање помаже у откривању грешака у раним фазама развоја, смањујући трошкове њиховог исправљања десетинама пута.
Главно
Unit тестирање је процес провере појединачних јединица (unit) изворног кода — функција, метода, класа — у изолацији од остатка програма. Сваки тест покреће одређени сценарио коришћења модула и проверава да ли резултат одговара очекиваном. Unit тестови се пишу на истом програмском језику као и основни код и извршавају се аутоматски у развојном окружењу или у CI/CD пајплајну. За разлику од интеграционих тестова, unit тестови не интерагују са стварним базама података, системом датотека или мрежним сервисима.
Основни циљ је брза повратна информација о исправности кода након промена. Ако програмер рефакторише метод, сет unit тестова потврђује да понашање није нарушено. Према Google Testing Blog (2025), пројекти са покривеношћу unit тестовима изнад 60% имају 2,5 пута мање инцидената у продукцији. Додатне предности: документација кода (тестови показују како користити API), поједностављење рефакторисања (можете мењати имплементацију задржавајући понашање) и брза дијагностика регресија.
Није сваки аутоматски тест unit тест. Критеријуми: тестира се један модул (класа или функција), спољне зависности замењене су моковима или стабовима, тест се извршава у милисекундама, не захтева подизање сервера или базе података. Тест који приступа стварној бази података је интеграциони тест. Тест који отвара прегледач је E2E тест. Разумевање граница између врста тестова је важно за правилну расподелу напора у пирамиди тестирања.
Квалитетни unit тестови следе FIRST принципе које је формулисао Robert C. Martin. Сваки тест треба да буде Fast (брз — милисекунде), Isolated (изолован — не зависи од других тестова), Repeatable (поновљив — исти резултат на свакој машини), Self-validating (самопроверљив — резултат „passed” или „failed”, без ручне провере) и Timely (благовремен — написан пре или истовремено са кодом). Кршење било ког принципа смањује вредност теста.
Стандардни шаблон за писање unit тестова. Arrange — припрема података и зависности: креирање објеката, подешавање мокова, задавање улазних параметара. Act — извршавање тестиране радње: позивање методе или функције. Assert — провера резултата: поређење стварне вредности са очекиваном. Подела на три блока чини тест читљивим и разумљивим. Ако Assert блок захтева сложену логику, тест вероватно проверава превише тога одједном.
// Пример unitтеста по AAA шаблону на Kotlin са JUnit 5
class CalculatorTest {
private lateinit var calculator: Calculator
@BeforeEach
fun setUp() {
// ARRANGE — креирамо тестирани објекат
calculator = Calculator()
}
@Test
fun addition_shouldReturnCorrectSum() {
// ACT — извршавамо радњу
val result = calculator.add(2, 3)
// ASSERT — проверавамо резултат
Assertions.assertEquals(5, result)
}
}
Име теста треба да опише шта се проверава и који је очекивани резултат. Формат: [methodName]_[scenario]_[expectedResult]. Пример: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Добро име теста замењује коментар и при паду одмах указује која функционалност је нарушена. Избегавајте имена попут test1, checkSomething или verify — она не носе информацију и отежавају дијагностику.
За изолацију тестираног модула од спољних зависности користе се тест дублери (test doubles). Главни типови: мокови (mocks) — проверавају да ли је одређени метод позван са одговарајућим параметрима; стабови (stubs) — враћају задате вредности при позиву методе; фејкови (fakes) — поједностављена имплементација стварне компоненте (нпр. InMemoryUserRepository уместо UserRepository који ради са базом података). Избор зависи од тога шта треба проверити: стање (стаб) или интеракцију (мок).
| Дублер | Шта проверава | Пример |
|---|---|---|
| Мок | Позив методе са исправним параметрима | userRepository.save(user) позван тачно 1 пут |
| Стаб | Повратна вредност | repository.findById(1) враћа User(id=1, name=„Test”) |
| Фејк | Логика кроз поједностављену имплементацију | InMemoryMapUserRepository са HashMap уместо базе података |
| Шпијун | Делимично моковање стварног објекта | spy(repo).when(findById).thenReturn(user) |
Mockito је најпопуларнији фрејмворк за моковање у Java и Kotlin. Омогућава креирање мокова преко mock(), подешавање повратних вредности преко when().thenReturn() и проверу позива преко verify(). Модерне верзије Mockito (5.x) подржавају статичке мокове (mockStatic) и поједностављену синтаксу преко BDDMockito (given-willReturn). Важно правило: не мокујте оно што није ваше — не креирајте мокове за вредносне објекте и стандардне библиотеке.
// Пример unitтеста са Mockito на Kotlin
class OrderServiceTest {
@Mock
private lateinit var paymentGateway: PaymentGateway
@Mock
private lateinit var userRepository: UserRepository
private lateinit var orderService: OrderService
@BeforeEach
fun init() {
MockitoAnnotations.openMocks(this)
orderService = OrderService(paymentGateway, userRepository)
}
@Test
fun processOrder_whenPaymentFails_shouldThrowException() {
// given
val user = User(id = 1, balance = 100.0)
val order = Order(amount = 200.0)
Mockito.`when`(paymentGateway.charge(any())).thenReturn(false)
Mockito.`when`(userRepository.findById(1)).thenReturn(user)
// when & then
assert Throws<PaymentException> {
orderService.processOrder(user.id, order)
}
// verify
Mockito.verify(paymentGateway).charge(any())
}
}
TDD (Test-Driven Development) — методологија у којој се тест пише пре имплементације кода. Циклус „Red-Green-Refactor”: напишите тест који пада (Red), напишите минимални код за пролазак теста (Green), побољшајте код без промене понашања (Refactor). TDD гарантује да је сав код покривен тестовима (coverage = 100% за написану функционалност) и да је код тестибилан — ако је код тешко тестирати, то значи да архитектура захтева побољшање.
Према истраживању IBM (2006-2026, лонгитудинална студија), тимови који користе TDD чине 40-80% мање дефеката у продукцији у поређењу са тимовима који пишу тестове након кода. TDD такође побољшава архитектуру: програмер мора да размишља о дизајну API-ја пре имплементације, што доводи до слабе повезаности (loose coupling) и високе кохезије (high cohesion). Додатни ефекат — документација живим кодом: тестови служе као спецификација понашања модула, која је увек актуелна.
TDD није увек оптималан. UI компоненте је тешко тестирати у изолацији — за њих су ефикаснији snapshot тестови или визуелно регресионо тестирање (Percy, Chromatic). Прототиповање и истраживање (spike solutions) не захтевају тестове. Legacy код без тестова тешко је покрити кроз TDD — овде су прво потребни characterization tests (тестови који фиксирају тренутно понашање пре рефакторисања). У овим случајевима TDD се не укида у потпуности, већ прилагођава — пишу се тестови за измењену функционалност, а не за цео legacy код.
Мобилни развој има специфику: пословна логика је често помешана са UI кодом (Activity, ViewController, ViewModel), што отежава unit тестирање. Најбоља пракса — танки прикази, дебели ViewModel-и: преместите сву логику из UI компоненти у одвојене класе (UseCase, Repository, ViewModel) које се лако тестирају без емулатора. За Android и iOS постоје изворни фрејмворци unit тестирања који раде на JVM/Native без покретања уређаја.
Android unit тестови се извршавају на локалној JVM без емулатора, што обезбеђује брзину извршавања — типичан тест траје мање од 100ms. JUnit 5 је главни runner. За ViewModel тестове користите kotlinx-coroutines-test за тестирање корутина и Turbine за тестирање StateFlow. Robolectric омогућава тестирање Android-зависних компоненти (Context, Resources) без емулатора, учитавајући shadow класе. За Compose тестове користите Compose UI Test — али то су већ UI тестови, не unit.
iOS unit тестови се пишу у Swift-у са XCTest (уграђен у Xcode). Quick + Nimble — BDD фрејмворци за читљивије тестове (describe/context/it). За моковање користите Cuckoo (генерација мокова) или SwiftyMocky. Swift подржава протоколе и injection зависности, што олакшава замену зависности. Кључна ствар: iOS unit тестови се извршавају на macOS симулатору, а не на стварном уређају. Тестови који захтевају хардверске функције (камера, Bluetooth) су интеграциони тестови.
Flutter unit тестови користе пакет flutter_test и извршавају се на Dart VM без емулатора. За моковање — пакет mockito са генератором кода (build_runner). Widget тестови (у истом пакету) тестирају појединачне виџете, али захтевају рендеровање и раде спорије — користите их само за проверу UI логике. Чиста Dart логика (модели, репозиторијуми, блокови) тестира се као обични Dart тестови без импортовања flutter_test.
// Пример unitтеста на Flutter-у са mockito
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([ApiClient])
import 'user_repository_test.mocks.dart';
void main() {
late MockApiClient mockApi;
late UserRepository repository;
setUp(() {
mockApi = MockApiClient();
repository = UserRepository(mockApi);
});
test('fetchUser returns user when API succeeds', () async {
// Arrange
final expectedUser = User(id: 1, name: 'Test');
when(mockApi.getUser(1))
.thenAnswer((_) async => expectedUser);
// Act
final result = await repository.fetchUser(1);
// Assert
expect(result, expectedUser);
verify(mockApi.getUser(1)).called(1);
});
}
Ефикасно unit тестирање захтева дисциплину. Главно правило: тестирајте понашање, а не имплементацију. Тест не треба да зна како је модул имплементиран изнутра (које приватне методе се позивају, којим редоследом). Ако је тест везан за имплементацију, ломи се при сваком рефакторисању и губи вредност. Тест проверава уговор: при улазу X треба да буде излаз Y. Изузетак — тестови за алгоритме са критичним перформансама, где је редослед позива важан.
100% покривеност је недостижан и непотребан циљ. Према Google Testing Blog (2025), оптималан ниво покривености за unit тестове је 70-80% линија кода. 100% покривеност се често постиже тестирањем гетера, сетера и конструктора, што не доноси вредност. Фокусирајте се на критичну пословну логику: сложена израчунавања, валидацију, обраду грешака, ивичне случајеве. Користите JaCoCo (Java), Coverage.py (Python), Istanbul (JS) за мерење и подесите праг у CI — пад израде при покривености мањој од 60%.
Unit тестови су прва фаза сваког CI/CD пајплајна. Извршавају се при сваком push-у у репозиторијум, пре израде и деплоја. Просечан временски оквир извршавања unit тестова у пројекту не би требало да прелази 5 минута — ако је дужи, тестови престају да буду „брзи” и програмери престају да их покрећу локално. Поделите тестове на брзе (unit) и споре (интеграционе) и покрећите их у различитим фазама пајплајна. Користите паралелно извршавање и fail-fast за убрзање.
Често постављана питања
Unit тест проверава један модул у изолацији, замењујући спољне зависности моковима. Интеграциони тест проверава интеракцију између више стварних компоненти (база података, API, систем датотека). Unit тестови се извршавају у милисекундама, интеграциони у секундама. У пирамиди тестирања unit тестови заузимају 70%.
Избор зависи од платформе: JUnit 5 за Java/Kotlin, XCTest за iOS/Swift, pytest за Python, Jest/Vitest за JavaScript/TypeScript, flutter_test за Flutter. За моковање користите Mockito (Java), Cuckoo (iOS), unittest.mock (Python) или vitest.mock (JS). Сви савремени фрејмворци подржавају параметризоване тестове, уграђене assert-е и паралелно покретање.
Fast — тест се извршава у милисекундама. Isolated — не зависи од других тестова и спољних система. Repeatable — даје исти резултат на свакој машини. Self-validating — проверава резултат аутоматски. Timely — написан пре или синхроно са кодом. Кршење макар једног принципа смањује ефикасност тестирања.
Да, обавезно. ViewModel садржи пословну логику — обраду догађаја, трансформацију података, управљање стањем. На Android-у користите kotlinx-coroutines-test за корутине и Turbine за тестирање StateFlow. На iOS-у тестирајте Combine Publishers или async/await у ViewModel. ViewModel тестови су чисти unit тестови који раде на JVM/macOS без емулатора.
Мрежни захтеви у unit тестовима се не извршавају — замењују се моковима HTTP клијента. На Android-у користите MockWebServer (OkHttp) — он подиже локални HTTP сервер, што је пожељније од мокова јер репродукује стварну мрежну интеракцију. MockWebServer пружа изолацију без губитка реалистичности. За iOS — OHHTTPStubs или URLProtocol за пресретање и замену одговора.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође