Alătură-te celor 500+ cursanți care deja își pun bazele să devină ingineri software.

Ce este testarea unitară: ghid complet

Postat 24 Ianuarie, 2026
Ce este testarea unitară: ghid complet

Testarea unitară este unul dintre cele mai importante concepte din testarea software și dezvoltarea aplicațiilor moderne. În acest articol vei înțelege ce este testarea unitară, de ce este importantă și cum se aplică în practică, cu exemple concrete.

Tipuri de testare software

Testarea software cea mai evidentă este procesul prin care “încercăm” manual o aplicație, sau un program software.

Manual înseamnă că executăm o acțiune prin input manual: fie click în interfața grafică, dacă vorbim de front-end, fie apelul unui endpoint, dacă vorbim de back-end.

Există și testare automată, prin care testăm o aplicație prin diferite metode automate, categorie la care se încadrează și testarea unitară.

Alte tipuri de testare automată

  • Testare de integrare
  • Testare de sistem (sau end-to-end)

Am mai făcut, de exemplu, în trecut, o comparație între testarea unitară și cea de integrare aici.

Ce este testarea unitară

Testarea unitară reprezintă cea mai granulară formă de testare software, adică se face pe bucăți de cod foarte mici.

În practică, testarea unitară se face pentru fiecare funcție sau metodă în parte, în izolare.

Spre deosebire de testarea manuală, de exemplu, testarea unitară se face tot prin folosirea de limbaje de programare și cod.

Există biblioteci speciale care se folosesc exclusiv pentru testare.

Cum funcționează testarea unitară

Spuneam că se testează funcții, sau metode în izolare, dar cum facem asta, având în vedere că metodele sunt de multe ori parte dintr-o clasă, a cărei stare o folosesc, sau primesc anumiți parametri de care au nevoie în execuția lor.

Pentru a putea reproduce toate scenariile posibile pentru o metodă anume, dependențele ei, fie valori pentru parametri, fie câmpuri ale clasei din care fac parte, sunt “simulați”.

Se folosesc valori simulate pentru a putea evalua exact care este efectul funcției respective în cadrul unui anumit scenariu și dacă efectul respectiv e cel așteptat sau nu.

De exemplu, dacă avem o funcție Sum(int x, int y) care calculează suma a două numere, ne așteptăm ca în scenariul în care îi pasăm valorile 2 și 3 să întoarcă rezultatul 5.

Așteptarea pe care noi o avem e condiția ca un test unitar să aibă succes sau să eșueze.

Exemple practice

Uite cum ar arăta un exemplu simplu pentru funcția Sum definită anterior.

// implementarea funcției
int Sum(int x, int y)
{
  return x + y;
}

// testul unitar
[Test]
void ShouldAddNumbers()
{
  var result = Sum(2, 3);

  // verificăm ca rezultatul să fie suma celor 
  // două numere
  Assert.Equal(5, result); // testul trece
}

Cu implementarea de mai sus, testul nostru unitar ShouldAddNumbers va trece, pentru că rezultatul întors este suma dintre cele două numere.

De ce este importantă testarea unitară

Ei bine, gândește-te că aplicația ta are sute sau mii de astfel de teste care urmăresc comportamentul tuturor metodelor implementate, astfel că nu e nevoie să o faci tu.

În eventualitatea în care cineva modifică una din implementări, iar rezultatul nu mai este cel pe care îl așteptam inițial, un test din suită va eșua și te va anunța de asta.

Uite un alt exemplu.

public class Order
{
  public List<Product> Cart { get; set; }
  public int TotalAmout { get; set; }
  public bool Completed { get; set; }
}

void CompleteOrder(Order order)
{
  const int MinimumOrderAmount = 100;
  if (order.TotalAmount < MinimumOrderAmount)
  {
    throw new InvalidOperationException("Total order amount is less than minimum amount");
  }
}

[Test]
void CompleteOrder_TotalAmountLessThanMinimumAmount_ThrowsException()
{
  // simulăm o comandă cu valoarea totală
  // sub cea minimă
  var order = new Order
  {
    TotalAmount = 99;
  }

  // verificăm că apelul CompleteOrder aruncă InvalidOperationException
  Assert.Throws<InvalidOperationException>(() => CompleteOrder(order));
}

Sigur, exemplele de mai sus sunt scrise în C#, dar te asigur că aceleași principii se aplică indiferent de limbajul de programare folosit.

Va diferi doar sintaxa și numele unor metode, precum cea prin care verificăm aruncarea excepției: Assert.Throws<InvalidOperationException>(() => CompleteOrder(order));.

Structura unui test unitar

Atunci când scriem teste unitare, există o convenție care e folosită aproape de toată lumea, care poartă numele de: Arrange, Act, Assert.

Sunt 3 pași pe care îi vei face aproape negreșit în orice test unitar.

Uite ce înseamnă fiecare:

Arrange - îți faci configurarea obiectelor pentru scenariul pe care urmează să-l testezi

Act - apelezi metoda pe care o testezi

Assert - verifici dacă efectul/rezultatul metodei e cel așteptat

Ordinea pașilor e importantă, pentru că nu poți apela metoda pe care o testezi până nu îți configurezi toate obiectele de care ai nevoie și nici nu poți evalua rezultatul până nu apelezi metoda, deci ordinea nu poate fi alta decât: Arrange, Act, Assert.

Simularea dependențelor

Dar să zicem că exemplele date până acum sunt ceva mai simpliste, pentru că metoda testată nu are dependențe directe, doar Order-ul care vine ca argument.

Dar cum ar fi dacă metoda noastră CompleteOrder ar avea un apel către o altă clasă, un alt serviciu?

Spuneam în introducere că scopul unui test unitar e să testeze câte o metodă în izolare și mai mult decât atât, fiecare clasă trebuie de asemenea testată în izolare.

Dacă în cadrul unei metode avem apelul către o altă metodă, dintr-o altă clasă, atunci principiul ăsta ar fi încălcat, pentru că testul nostru ar testa de fapt implicit și metoda din clasa cealaltă.

class OrderProcessor
{
  private IInventoryService _inventoryService;

  public OrderProcessor(IInventoryService inventoryService)
  {
    _inventoryService = inventoryService;
  }

  void CompleteOrder(Order order)
  {
    // verificăm disponibilitatea în stock a produselor comandate
    List<Product> unavailableProducts = _inventoryService.CheckAvailability(order.Cart);
    
    //.. aici facem ceva legat de produsele indisponibile

    const int MinimumOrderAmount = 100;
    if (order.TotalAmount < MinimumOrderAmount)
    {
      throw new InvalidOperationException("Total order amount is less than minimum amount");
    }
  }
}

Asta e o problemă, pentru că, în general, graful de dependențe va crește cu timpul și nu vrem să testăm un lanț întreg de apeluri într-un singur test unitar.

Vrem să testăm fiecare metodă din fiecare clasă izolat de orice altceva, pentru că altfel ne-am putea lovi de tot felul de inconsistențe care ne-ar face să ne pierdem încrederea în suita noastră de teste.

Și atunci ce facem?

Ne “simulăm” dependențele printr-un proces care se numește mocking.

PS. ca să poți face asta trebuie ca design-ul codului tău să urmeze principiul denumit “Dependency Injection”, am scris despre asta aici.

Sunt foarte multe detalii legate de mocking, dar pentru moment vreau doar să-ți faci o idee generală despre ce este și cum se face.

Din fericire, bibliotecile pentru unit testing ne pun la dispoziție posibilitatea de a configura servicii de tip mock cu foarte mare ușurință.

[Test]
void CompleteOrder_TotalAmountLessThanMinimumAmount_ThrowsException()
{
  // Arrange
  var inventoryService = new Mock<IInventoryService>();
  // în loc de serviciul real, pasăm aici mock-ul
  var orderProcessor = new OrderProcessor(inventoryService.Object);

  var order = new Order
  {
    TotalAmount = 99;
  }

  // Act & assert - apelăm și metoda testată, validăm și rezultatul
  Assert.Throws<InvalidOperationException>(
    () => orderProcessor.CompleteOrder(order)
  );
}

Avantajul e că acum putem să schimbăm comportamentul obiectului pentru IInventoryService după bunul plac, pentru a reproduce diferite scenarii în teste, fără să fie nevoie să alterăm implementarea lui reală.

Desigur, vei face asta pentru toate dependențele unei clase pe care o testezi pentru a avea control absolut asupra lor și a nu testa totodată și implementările acelor clase.

Astfel obții izolare și 0 inconsistențe, ceea ce face ca suita ta de teste să fie ca un gardian împotriva potențialelor defecte.

Concluzie

Testarea unitară e un subiect foarte complex și vast, se pot face cursuri întregi pe tema asta, dar odată ce ai deprins ideile de bază îți va fi destul de ușor să scrii teste în majoritatea cazurilor.

Sigur, asta îți va tăia din viteza de dezvoltare, dar e extrem de important să poți verifica în 2 secunde dacă o implementare pe care ai făcut-o a stricat cumva ceva prin codul existent.

Aici e și una din situațiile în care recomand cu tărie folosirea modelelor AI pentru generarea codului.

Tu faci implementările și lași AI să-ți scrie testele, iar tu doar le verifici.

Astfel nu pierzi nici foarte multă viteză de dezvoltare și obții și un set de teste pe care să te bazezi în viitor.

Win-win!

Atât pentru moment, nu ezita să-mi scrii dacă e ceva ce n-ai înțeles sau ești interesat să înveți mai multe.

Hai în comunitate

Strategii de carieră și concepte tehnice explicate pe înțelesul tău.