Testarea unitară în dezvoltarea mobilă: ce este, metode şi frameworkuri

Autor: IT Sectr Publicat: 2026-04-06 Timp de citire: 9 min

Testarea unitară este o metodă de verificare a software-ului prin care se testează corectitudinea modulelor individuale sau a funcţiilor de cod în izolare faţă de restul sistemului. Potrivit lui Martin Fowler, 2026, testele unitare sunt fundamentul CI/CD şi al refactorizării, oferind feedback rapid despre funcţionarea codului. Testarea modulară ajută la detectarea erorilor în fazele incipiente ale dezvoltării, reducând costul remedierii acestora de zeci de ori.

Principalele aspecte

  • Test unitar — verificarea unui singur modul (funcţie, metodă, clasă) în izolare de dependenţele externe
  • Principiile FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — baza testelor de calitate
  • Mockuri şi stuburi — înlocuitori ai dependenţelor externe (bază de date, API, sistem de fişiere), asigurând izolarea testului
  • TDD (Test-Driven Development) — metodologia dezvoltării prin testare: roşu-verde-refactorizare
  • Piramida testelor — testele unitare ocupă 70% din piramidă, oferind feedback rapid la fiecare commit

Ce este testarea unitară?

Testarea unitară este procesul de verificare a unităţilor individuale (unit) ale codului sursă — funcţii, metode, clase — în izolare de restul programului. Fiecare test execută un scenariu specific de utilizare a modulului şi verifică dacă rezultatul corespunde celui aşteptat. Testele unitare se scriu în acelaşi limbaj de programare ca şi codul principal şi se execută automat în mediul de dezvoltare sau în pipeline-ul CI/CD. Spre deosebire de testele de integrare, testele unitare nu interacţionează cu baze de date reale, sistemul de fişiere sau servicii de reţea.

De ce sunt necesare testele unitare?

Scopul principal este feedback-ul rapid despre corectitudinea codului după modificări. Dacă un dezvoltator refactorizează o metodă, setul de teste unitare confirmă că comportamentul nu s-a stricat. Potrivit Google Testing Blog (2025), proiectele cu acoperire a testelor unitare peste 60% au de 2,5 ori mai puţine incidente de producţie. Avantaje suplimentare: documentarea codului (testele arată cum să foloseşti API-ul), simplificarea refactorizării (poţi schimba implementarea păstrând comportamentul) şi diagnosticarea rapidă a regresiunilor.

Ce se consideră test unitar?

Nu orice test automat este un test unitar. Criterii: se testează un singur modul (clasă sau funcţie), dependenţele externe sunt înlocuite cu mockuri sau stuburi, testul se execută în milisecunde, nu necesită pornirea unui server sau a unei baze de date. Un test care accesează o bază de date reală este un test de integrare. Un test care deschide un browser este un test E2E. Înţelegerea graniţelor dintre tipurile de teste este importantă pentru distribuirea corectă a eforturilor în piramida testării.

Principiile FIRST şi structura AAA

Testele unitare de calitate respectă principiile FIRST formulate de Robert C. Martin. Fiecare test trebuie să fie Fast (rapid — milisecunde), Isolated (izolat — nu depinde de alte teste), Repeatable (repetabil — acelaşi rezultat pe orice maşină), Self-validating (autoverificabil — rezultatul este „passed” sau „failed”, fără verificare manuală) şi Timely (oportun — scris înainte sau simultan cu codul). Încălcarea oricărui principiu reduce valoarea testului.

Structura AAA (Arrange-Act-Assert)

Şablonul standard pentru scrierea testelor unitare. Arrange — pregătirea datelor şi a dependenţelor: crearea obiectelor, configurarea mockurilor, setarea parametrilor de intrare. Act — executarea acţiunii testate: apelarea metodei sau funcţiei. Assert — verificarea rezultatului: compararea valorii reale cu cea aşteptată. Împărţirea în trei blocuri face testul lizibil şi uşor de înţeles. Dacă blocul Assert necesită logică complexă, testul probabil verifică prea multe lucruri deodată.

kotlin
// Exemplu de test unitar după şablonul AAA în Kotlin cu JUnit 5
class CalculatorTest {

    private lateinit var calculator: Calculator

    @BeforeEach
    fun setUp() {
        // ARRANGE — creăm obiectul testat
        calculator = Calculator()
    }

    @Test
    fun addition_shouldReturnCorrectSum() {
        // ACT — executăm acţiunea
        val result = calculator.add(2, 3)

        // ASSERT — verificăm rezultatul
        Assertions.assertEquals(5, result)
    }
}

Denumirea testelor

Numele testului trebuie să descrie ce se verifică şi care este rezultatul aşteptat. Format: [methodName]_[scenario]_[expectedResult]. Exemplu: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Un nume bun de test înlocuieşte comentariul şi la eşec indică imediat ce funcţionalitate este încălcată. Evitaţi nume precum test1, checkSomething sau verify — nu transmit informaţii şi îngreunează diagnosticarea.

Mockuri, stuburi şi fake-uri: ce şi când să folosiţi

Pentru izolarea modulului testat de dependenţele externe se folosesc dubluri de test (test doubles). Tipurile principale: mockuri (mocks) — verifică dacă o anumită metodă a fost apelată cu parametrii corecţi; stuburi (stubs) — returnează valori prestabilite la apelarea metodei; fake-uri (fakes) — implementare simplificată a unui component real (de exemplu, InMemoryUserRepository în loc de UserRepository care lucrează cu baza de date). Alegerea depinde de ce trebuie verificat: starea (stub) sau interacţiunea (mock).

DublurăCe verificăExemplu
MockApelarea metodei cu parametrii corecţiuserRepository.save(user) a fost apelat exact 1 dată
StubValoarea returnatărepository.findById(1) returnează User(id=1, name=„Test”)
FakeLogica prin implementare simplificatăInMemoryMapUserRepository cu HashMap în loc de bază de date
SpionMocking parţial al unui obiect realspy(repo).when(findById).thenReturn(user)

Mockito: exemplu de mock în Java/Kotlin

Mockito este cel mai popular framework de mock în Java şi Kotlin. Permite crearea de mockuri prin mock(), configurarea valorilor returnate prin when().thenReturn() şi verificarea apelurilor prin verify(). Versiunile moderne de Mockito (5.x) suportă mockuri statice (mockStatic) şi sintaxă simplificată prin BDDMockito (given-willReturn). O regulă importantă: nu mocka ceea ce nu este al tău — nu crea mockuri pentru obiecte-valoare şi biblioteci standard.

kotlin
// Exemplu de test unitar cu Mockito în 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: dezvoltare prin testare

TDD (Test-Driven Development) — metodologia în care testul se scrie înainte de implementarea codului. Ciclul „Red-Green-Refactor”: scrie un test care eşuează (Red), scrie codul minim pentru a trece testul (Green), îmbunătăţeşte codul fără a schimba comportamentul (Refactor). TDD garantează că tot codul este acoperit de teste (acoperire = 100% pentru funcţionalitatea scrisă) şi că codul este testabil — dacă codul este greu de testat, înseamnă că arhitectura necesită îmbunătăţiri.

Avantajele TDD

Potrivit cercetărilor IBM (2006-2026, studiu longitudinal), echipele care folosesc TDD au cu 40-80% mai puţine defecte în producţie comparativ cu echipele care scriu teste după cod. TDD îmbunătăţeşte de asemenea arhitectura: dezvoltatorul trebuie să se gândească la design-ul API înainte de implementare, ceea ce duce la cuplare slabă (loose coupling) şi coeziune ridicată (high cohesion). Un efect suplimentar — documentare prin cod viu: testele servesc ca specificaţie a comportamentului modulului, care este mereu actualizată.

Când nu este potrivit TDD?

TDD nu este întotdeauna optim. Componentele UI sunt greu de testat în izolare — pentru ele sunt mai eficiente testele snapshot sau testele de regresie vizuală (Percy, Chromatic). Prototiparea şi cercetarea (spike solutions) nu necesită teste. Codul legacy fără teste este greu de acoperit prin TDD — aici sunt necesare mai întâi characterization tests (teste care fixează comportamentul curent înainte de refactorizare). În aceste cazuri, TDD nu este complet abandonat, ci adaptat — se scriu teste pentru funcţionalitatea modificată, nu pentru tot codul legacy.

Testarea unitară în aplicaţii mobile

Dezvoltarea mobilă are specificul ei: logica de business este adesea amestecată cu codul UI (Activity, ViewController, ViewModel), ceea ce complică testarea unitară. Cea mai bună practică — view-uri subţiri, ViewModel-uri groase: mutaţi toată logica din componentele UI în clase separate (UseCase, Repository, ViewModel) care pot fi testate uşor fără emulator. Pentru Android şi iOS există frameworkuri native de testare unitară care rulează pe JVM/Native fără a porni dispozitivul.

Teste unitare pe Android (JUnit + Mockito/Robolectric)

Testele unitare Android se execută pe JVM local fără emulator, ceea ce asigură viteză de execuţie — un test tipic durează sub 100ms. JUnit 5 este runner-ul principal. Pentru testele ViewModel folosiţi kotlinx-coroutines-test pentru testarea corutinelor şi Turbine pentru testarea StateFlow. Robolectric permite testarea componentelor dependente de Android (Context, Resources) fără emulator, încărcând clase shadow. Pentru testele Compose folosiţi Compose UI Test — dar acestea sunt deja teste UI, nu unitare.

Teste unitare pe iOS (XCTest + Quick/Nimble)

Testele unitare iOS se scriu în Swift cu XCTest (încorporat în Xcode). Quick + Nimble — frameworkuri BDD pentru teste mai lizibile (describe/context/it). Pentru mocking folosiţi Cuckoo (generare de mockuri) sau SwiftyMocky. Swift suportă protocoale şi injectarea dependenţelor, ceea ce facilitează înlocuirea dependenţelor. Aspect cheie: testele unitare iOS rulează pe simulatorul macOS, nu pe un dispozitiv real. Testele care necesită funcţii hardware (cameră, Bluetooth) sunt teste de integrare.

Teste unitare pe Flutter (flutter_test + Mockito)

Testele unitare Flutter folosesc pachetul flutter_test şi rulează pe Dart VM fără emulator. Pentru mocking — pachetul mockito cu generator de cod (build_runner). Testele de widgeturi (în acelaşi pachet) testează widgeturi individuale, dar necesită randare şi rulează mai lent — folosiţi-le doar pentru verificarea logicii UI. Logica Dart pură (modele, repository-uri, blocuri) se testează ca teste Dart obişnuite fără a importa flutter_test.

dart
// Exemplu de test unitar pe Flutter cu 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);
    });
}

Cele mai bune practici şi erori tipice

Testarea unitară eficientă necesită disciplină. Regula principală: testaţi comportamentul, nu implementarea. Testul nu trebuie să ştie cum este implementat modulul intern (ce metode private sunt apelate, în ce ordine). Dacă testul este legat de implementare, se strică la fiecare refactorizare şi şi pierde valoarea. Testul verifică un contract: la intrarea X trebuie să avem ieşirea Y. Excepţie — testele pentru algoritmi cu performanţă critică, unde ordinea apelurilor este importantă.

  • O singură verificare per test — un assert sau un grup de asserturi conexe pentru o singură verificare logică
  • Evitaţi duplicarea — folosiţi @BeforeEach / setUp pentru iniţializarea comună, teste parametrizate pentru date de intrare diferite
  • Nu testaţi metodele private — testaţi prin API-ul public. Dacă o metodă privată nu este acoperită, înseamnă că logica ei nu este vizibilă din exterior
  • Acoperiţi cazurile limită — colecţii goale, null/undefined, numere negative, valori maxime
  • Nu folosiţi Thread.sleep în teste — face testele lente şi instabile. Folosiţi timeouturi de test şi corutine

Ce nivel de acoperire este suficient?

100% acoperire este un scop de neatins şi inutil. Potrivit Google Testing Blog (2025), nivelul optim de acoperire pentru testele unitare este de 70-80% din liniile de cod. 100% acoperire se atinge adesea prin testarea getterelor, setterelor şi constructorilor, ceea ce nu aduce valoare. Concentraţi-vă pe logica de business critică: calcule complexe, validare, gestionarea erorilor, cazuri marginale. Folosiţi JaCoCo (Java), Coverage.py (Python), Istanbul (JS) pentru măsurare şi setaţi un prag în CI — eşecul build-ului la acoperire sub 60%.

CI/CD şi testele unitare

Testele unitare sunt prima etapă a oricărui pipeline CI/CD. Se execută la fiecare push în repository, înainte de build şi deploy. Timpul mediu de execuţie a testelor unitare într-un proiect nu trebuie să depăşească 5 minute — dacă este mai lung, testele încetează să fie „rapide” şi dezvoltatorii încetează să le ruleze local. Împărţiţi testele în rapide (unitare) şi lente (de integrare) şi rulaţi-le în etape diferite ale pipeline-ului. Folosiţi execuţia paralelă şi fail-fast pentru accelerare.

Întrebări frecvente

Cu ce diferă testul unitar de cel de integrare?

Testul unitar verifică un singur modul în izolare, înlocuind dependenţele externe cu mockuri. Testul de integrare verifică interacţiunea dintre mai multe componente reale (bază de date, API, sistem de fişiere). Testele unitare se execută în milisecunde, cele de integrare în secunde. În piramida testării, testele unitare ocupă 70%.

Ce framework să aleg pentru testele unitare?

Alegerea depinde de platformă: JUnit 5 pentru Java/Kotlin, XCTest pentru iOS/Swift, pytest pentru Python, Jest/Vitest pentru JavaScript/TypeScript, flutter_test pentru Flutter. Pentru mocking folosiţi Mockito (Java), Cuckoo (iOS), unittest.mock (Python) sau vitest.mock (JS). Toate frameworkurile moderne suportă teste parametrizate, aserţiuni încorporate şi executare paralelă.

Ce sunt principiile F.I.R.S.T. în testare?

Fast — testul se execută în milisecunde. Isolated — nu depinde de alte teste şi sisteme externe. Repeatable — dă acelaşi rezultat pe orice maşină. Self-validating — verifică rezultatul automat. Timely — scris înainte sau sincron cu codul. Încălcarea oricărui principiu reduce eficienţa testării.

Trebuie să scriu teste unitare pentru ViewModel în Android/iOS?

Da, obligatoriu. ViewModel conţine logica de business — gestionarea evenimentelor, transformarea datelor, gestionarea stării. Pe Android folosiţi kotlinx-coroutines-test pentru corutine şi Turbine pentru testarea StateFlow. Pe iOS testaţi Combine Publishers sau async/await în ViewModel. Testele ViewModel sunt teste unitare pure care rulează pe JVM/macOS fără emulator.

Cum testez codul cu cereri de reţea?

Cererile de reţea în testele unitare nu se execută — sunt înlocuite cu mockuri ale clientului HTTP. Pe Android folosiţi MockWebServer (OkHttp) — acesta porneşte un server HTTP local, ceea ce este preferabil mockurilor deoarece reproduce interacţiunea reală de reţea. MockWebServer oferă izolare fără a pierde realismul. Pentru iOS — OHHTTPStubs sau URLProtocol pentru interceptarea şi înlocuirea răspunsurilor.

Concluzii

  • Testarea unitară — verificarea modulelor individuale în izolare de dependenţele externe cu feedback rapid
  • Structura AAA — Arrange (pregătire), Act (acţiune), Assert (verificare) — şablonul standard de test
  • Mockuri şi stuburi — dubluri de test pentru izolare: mockurile verifică apelurile, stuburile returnează valori
  • TDD — dezvoltare prin testare (Red-Green-Refactor) reduce defectele cu 40-80%
  • Principiile FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — baza unui test de calitate
  • Instrumente platformă — JUnit 5 (Android), XCTest (iOS), flutter_test (Flutter), Jest (React Native)
  • Acoperire 70-80% — nivel optim pentru logica de business critică, getterele şi setterele nu necesită teste

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și