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 TestCase
u, 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í.