9. června 2008

On se ještě používá jUnit? Vždyť máme TestNG.

Konečně jsem si poslechl záznam přednášky Jana Novotného Automatické testování v praxi. Jen více takových ...

Ale proč píšu tento příspěvek? Stále mě překvapuje jak velké množství lidí neustále používá jUnit, který mi přijde v porovnání s TestNG jak chudý příbuzný. Proto jsem se rozhodl nastínit 2 hlavní výhody TestNG, alespoň z mého pohledu.

Přecházel jsem na TestNG v době, kdy po jUnit4 nebylo ani vidu ani slechu, takže jsem měl rozhodování o hodně jednodušší. Ale i po uvolnění jUnit4 je TestNG lepší. Ovšem jako jUnit4 vyžaduje Javu 1.5, takže pro vás co běžíte na Javě 1.4 a méně, pak máte smůlu (ovšem testy můžete překládat jinou verzí Javy než produkční kód).

Na úvod porovnání uvedu: jako vývojové nástroje používám Eclipse, builduji Antem nebo Mavenem a necítím se být nijak limitován užíváním TestNG oproti jUnitu.

Nyní k výhodám. Hlavní skutečnost, která mě hnala za změnou jUnitu za něco jiného je skutečnost, kterou se sice podařilo vyřešit, ale neskutečmě se mi nelíbila. Jde o to, že každý test nejenom dostává vlastní novou instanci TestCaseu, ale tato instance je zároveň použita jako objekt nesoucí informaci o výsledku testu (proběhl / neproběhl). Proč mi to vadí? Protože pokud máte hodně testů a u hodně z nich si naplníte instanční proměnné nějakými instancemi, pak vám běh testů sežere hodně paměti (protože se instance TestCaseů neuvolní pro garbage collection), pokud nespadnou na nedostatek paměti. Takže buď musíte ručně nastavovat hodnotu null do všech instančních proměnných (pracné a náročné na nezapomenutí) a nebo mít připraveného předka, který bude mít tearDown s funkcí nastavování hodnoty null instančním proměnným.

Druhou obrovskou výhodou, kterou jsem dokázal ocenit až po chvíli užívání testNG jsou data-providery. jUnit4 nabízí něco podobného v podobě parametrizovaných testů, ovšem to co nabízí TestNG se to podobá hodně vzdáleně. Když se podíváme na příklad z článku o jUnit4 na devx.com:

@RunWith(Parameterized.class)
public class SquareTest {

private static Calculator calculator = new Calculator();
private int param;
private int result;

@Parameters
public static Collection data() {
return Arrays.asList(new Object[][]{ {0, 0}, {1, 1}, {2, 4}, {4, 16} });
}

public SquareTest(int param, int result) {
this.param = param;
this.result = result;
}

@Test
public void square() {
calculator.square(param);
assertEquals(result, calculator.getResult());
}

}
Z příkladu je zřejmé, že jUnit parametrizuje na úrovní třídy, protože parametry se ze statické metody předávají do konstruktoru třídy. Naproti tomu TestNG:
public class SquareTest {

private Calculator calculator = new Calculator();

@DataParameter(name = "square")
protected Object[][] data() {
return new Object[][]{ {0, 0}, {1, 1}, {2, 4}, {4, 16} };
}

@Test(dataProvider = "square")
public void square(int param, int result) {
calculator.square(param);
assertEquals(calculator.getResult(), result);
}

}
Data providerů můžu mít v TestNG víc, dokonce může data provider být z jiné třídy, než test, který jej využívá. Jediné, co si mi nelíbilo, je přehození parametrů metod assert, tj. první je actual a druhý expected. Naštěstí existuje třída AssertJUnit, která zachovává zvyklost z jUnitu.

Tyto dvě skutečnosti jsou pro mě tak silnými argumety, že jsem přešel a nelituji. Navíc použití data providerů je tak jednoduché, že dnes je používám možná více než je zdrávo, ale když jsou tak elegantní.