fbpx

Prečo musí byť test červený?

Každý dobre vie, že testy máme na to, aby sme vedeli, či aplikácia funguje. Teda musia byť stále zelené. A keď nejaký test očervenie, tak máme problém. Treba ho opraviť. (Aspoň dúfam, že ste na vašom projekte nerezignovali a nezačali ignorovať zlyhávajúce testy.)

Tak prečo máme pri Test-Driven Developmente (TDD) povinnosť skontrolovať, že je test na začiatku červený? No predsa, aby sme vedeli, že niečo testuje.

„Veď chceme aby bol zelený, či nie?”

Pred mnohými rokmi sa mi stalo, že som robil kolegovi code review. Kód vyzeral fajn a robil čo mal. Potom mi ukázal, že testy prechádzajú, vrátane toho nového čo pridal. Tak som sa spýtal, či ho videl zlyhať. „Nie, načo?” bola odpoveď ktorú som pochopil ako výzvu. Zakomentoval som obsah celej metódy a nechal len vrátenie prázdnej kolekcie. Myslím, že sme boli prekvapení obaja, že test aj napriek tomu prešiel. Ja som bol prekvapený preto, že som nečakal, že pointu červeného testu ukážem takto ľahko.

Čo sa vlastne stalo? Stalo sa to, že nemal dosť testov a ten jediný čo mal nepredpokladal, že zoznam vrátený z metódy bude prázdny. Prázdny zoznam mohol byť testovaný v inom teste, ale nebol. Predpokladám, že test napísal až po tom, čo bola funkcionalita spravená a otestovaná, takže bol so sebou spokojný. Aby uspokojil aj firemné štandardy, tak spravil aj test, nech sa niečo testuje.

Takže akým chybám vieme predísť?

Pozrime sa na najčastejšie dôvody, prečo môže test testovať menej ako si myslíme.

Smoke test

Najjednoduchší spôsob ako urobiť nekvalitný test je zavolať testovanú metódu a potom nič neskontrolovať. Takýto test je schopný zlyhať len ak nejaká (neskôr zavedená) chyba spôsobí výnimku. Ale nedá nám žiadnu istotu, že to čo sa v testovanej metóde robí dosahuje očakávané výsledky. Jeho názov to aj naznačuje — niečo zavoláme a ak to nezadymí tak sme spokojní 1.

Predpokladám, že najčastejší dôvod prečo ľudia takéto testy píšu je, že testovaný komponent nie je ľahko testovateľný, alebo len naháňajú vyššie pokrytie kódu testami. A práve to je na nich to nebezpečné. Keď totiž idem refaktorovať nejaký väčší cudzí kód, tak sa viem pozrieť na aktuálne pokrytie testami, ale code coverage mi nepovie, či tých 15 krát, čo to tade prešlo bolo aj skontrolované nejakým assertom. Ale keď vidím číslo 15, tak predpokladám, že môžem bezpečne refaktorovať.

Chýba verify pri mockovacích knižniciach

Toto je špeciálny prípad k predchádzajúcemu bodu. Častá začiatočnícka chyba pri knižnici EasyMock je, že pred koncom testu treba na mocku zavolať metódu verify. Ak ju nezavoláme, tak užitočnosť mocku klesne na testovanie nepriamych vstupov, ak majú vplyv na assertovaný výstup. V prípade EasyMock sa dá spoľahnúť na verifikáciu v @After pomocou EasyMockSupport.verifyAll.

Nevhodný if

Čo je problém s nasledujúcim testom?

@Test
public void managerCanDecideWhomToFire() {
	// prepare data used for tested
	Employee toFire = tested.getLeastPerformingEmployee();
	if (toFire != null) {
		assertEquals("Incorrect name", "John Doe", toFire);
	}
} 

Že test nezachytí situáciu, kedy žiadny zamestnanec nespĺňa logiku na prepustenie. V tomto prípade by asi stačilo vynechať if, ale častokrát to nie je také ľahké. Na druhej strane, ak je null správna hodnota pri určitých vstupoch (povedzme, keď všetci zamestnanci prekonali určitý výkonový prah, alebo všetci bez rozdielov makajú rovnako), tak si to zaslúži vlastný test.

Cykly while alebo for

Podobná situácia môže nastať aj keď máme testovať vrátenú kolekciu:

@Test
public void () {
	// setup data for test here 
	Collection<Employee> slacking = tested.employeesBelowBillingHours(120);
	for(Employee each: slacking) {
		assertEquals("Incorrect name", "John Doe", each);
	}
}

Takýto test opäť zlyháva pri prázdnom zozname, ale navyše zlyhá ak ten istý zamestnanec je v zozname 2x. Oprava je jednoduchá — pridáme overenie, že kolekcia má očakávanú veľkosť:

assertEquals("Incorrect number of employees", 1, slacking.size());

Dá sa to ešte zlepšiť, aby JUnit vypísal viac detailov v chybe, za predpokladu, že máme implementovaný toString a primeranú implementáciu equals na zamestnancovi:

@Test
public void () {
	// setup data for test here 
	Collection<Employee> slacking = tested.employeesBelowBillingHours(120);

	Collection<Employee> expected = new ArrayList<Employee>();
	expected.add(new Employee("John Doe");
        expected.add(new Employee("Jane Doe"));

	assertEquals(expected, slacking);
}

Ak v projekte používame knižnicu AssertJ, tak náš pracovný život bude oveľa lepší. Pozrite si nasledujúci test:

assertThat(slacking)
	.extracting(Employee::getName)
	.containsOnly("John Doe", "Jane Doe");
	

Všimnite si, že v pri takto formulovanom asserte nie sme závislí na správnej implementácii equals. Okrem toho sme otestovali veľkosť kolekcie a aj to, že obsahuje práve požadovaných zamestnancov. A to všetko čitateľným spôsobom.

Ako na to, keď nepoužívam TDD?

Ak píšeme testy pre existujúcu funkcionalitu, je veľmi lákavé skončiť hneď ako je test napísaný a po spustení zelený. Málokto ide spraviť úmyselnú chybu do produkčného kódu aby si overil, či test funguje. A aj tí čo to spravia skúsia len jednu vec. Môžu mať šťastie a nájsť slabé miesto v teste.

Lepšie je skúsiť postupne viac chýb. Je to viac otravné a z programátora sa stáva tester, ktorý sa pokúša nájsť slabé miesta. Nie každému to vyhovuje, lebo veľa programátorov chce vytvárať nové veci a nie kaziť fungujúce riešenie. Psychológia…

Keď už máme dosť skúseností je možné spraviť kompromis. Ale nie je to ideálne — ani pokazenie produkčného kódu ani nájdenie kompromisu, kedy to nemusíme robiť. Naproti tomu, TDD automaticky produkuje oveľa kvalitnejšie testy.

Záver

Presvedčil som vás, že vždy zelený test nie je až taký dobrý nápad? Spoznali ste v niektorom z príkladov, čo som ukázal, svoje výtvory? Ak áno, je to v poriadku, aj ja som kedysi písal takéto testy. Dôležité je, že som sa naučil dávať si pozor na situácie, kedy môžem spraviť test, ktorý vyzerá užitočne, ale nie je. Ďalej je dôležité uvedomiť si, že v čase keď píšem testy až po produkčnom kóde, tak treba byť ostražitejší.


  1. Názov pochádza z prostredia elektrotechniky. Keď dokončíme nejaký elektronický obvod a prvý krát ho zapojíme do elektriny a obvod nezhorí, tak sme na dobrej ceste k úspechu.

1 komentár k “Prečo musí byť test červený?”

Nie je možné pridávať komentáre.