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í.

7 komentářů:

Anonymní řekl(a)...

Díky za reakci. K TestNG jsem měl dlouho nedůvěru, protože s podporou v nástrojích to bylo vždycky trošku problematičtější. Dnes již možná tento argument padl, nicméně zvyk je železná košile.

Parametrizovtelné testy jsem zatím neměl extra potřebu použít, takže tohle by pro mě asi argument moc nebyl. Každopádně zmínka o GC a držení instancí mě velmi zaujala. Vždycky jsem si myslel, že instance testovací třídy je po dokončení testu zahozena. Pravda je, že jsem skutečně v poslední době řešil problémy s paměťovou náročností testů a že by mohla být příčina tohle mě absolutně nedokáplo. O JUnitu jsem toho přečetl hodně, koukal i do zdrojáků, ale na takovéhle chování jsem zatím nenarazil. Takže díky za tip, tohle se MUSÍ PROVĚŘIT ;-) .

Jira řekl(a)...

Koneckonců proč to tajit. Naše tearDown metoda vypadala:

protected void tearDown() throws Exception {

Class<?> clazz = this.getClass();

while (!clazz.equals(TestCase.class)) {

java.lang.reflect.Field[] f = clazz.getDeclaredFields();

for (int i = 0; i < f.length; i++) {

Field field = f[i];

if (!field.getType().isPrimitive() && ((field.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) == 0)) {

field.setAccessible(true);

field.set(this, null);

}

}

clazz = clazz.getSuperclass();

}

super.tearDown();

}

Anonymní řekl(a)...

Moc hezký - připomíná mi to přísloví:

Nejde to? Vem si kladivo. Pořád to nejde? Vem si větší kladivo.

A tohle patří do segmentu větších kladiv :-D .

Anonymní řekl(a)...

TestNG jsme dost dlouho snažili rozumně rozchodit asi před půl rokem a nakonec jsme to vzdali.

Hlavní problém byl v integraci s Mavenem. Surefire (aspoň v té době) nefungoval s pětkovou (a nevím jestli s čtyřkovou) řadou TestNG, takže jsme používali snapshot verzi Surefire.. To znamenalo, že se nám občas z ničeho nic rozbil build - snapshot verze totiž ne vždycky fungovala.

Taky si vzpomínám, že jsme u Surefire+TestNG měli problémy s classloaderem (jiná pravidla načítání resourců při běhu z IDE a ze Surefire) a celou řadu dalších "podivných" problémů, hlavně při použití dalších pluginů (mám v matné paměti problém s Coberturou).

Taky nám testy nefungovali v Hudsonu (padalo to při parsování XML reportů o testech, pokud byly vygenerovány v TestNG), což byla poslední kapka, po které jsme to vzdali.

Samozřejmě nic z toho nebyl problém TestNG ale zmíněných toolů, a možná, že dneska už nic z toho neplatí, ale stejně bych dneska byl dost opatrný a nenechal se hned zlákat, že TestNG se vším běží jako hodinky.

Anonymní řekl(a)...

O TestNG jsem také přemýšlel. Dokonce v některých případech to byla jediná rozumná možnost testování (viz. JBoss Seam).

Bohužel však, podpora v NetBeans je nulová, což mě nakonec přinutilo TestNG nepoužívat.

Jira řekl(a)...

Našel jsem následující příspěvek na Cedricově blogu: TestNG and NetBeans walk into a bar..., takže bych řekl, že by mohla existovat možnost jak použít TestNG v NetBeans.

Unknown řekl(a)...

Zkoumal jste jestli ma TestNG nejak vyresene "in container" testy, tedy jestli pro nej existuje neco jako JUnitEE, ktery za me pusti testy, ktere testuji kod, ktery musi bezet v applikacnim serveru?
Diky, Lukas