tifyty

pure Java, what else ?

Szegény ember JsUnit-ja

Szegény ember JsUnit-ja

Mondják: van, amiben pont a gazdagok szűkölködnek. Nem népmeséről van szó, hanem hogy bizonyos cégek, tipikusan a pénzügyi szektorban, szigorúan leválasztják a belső hálót a külsőről. Emiatt talán biztonságosabbak lesznek a rendszerek. Szintén emiatt van, hogy nem lehet csakúgy akármit letölteni. Ha a senior fejlesztő valamit fel akar használni, akkor ahhoz meg kell győznie a főnökét, a főnöke főnökét, majd a biztonsági vezetőt is. Ez egy hosszú és bizonytalan kimenetű folyamat, úgyhogy a senior fejlesztő gyakran inkább gányol, újra feltalálja a kereket, rosszul.

A projekt

Kicsit konkrétabban: néhány hónapja szembe jött egy javascriptes projekt. Egészen jól el volt eresztve, mert használhatott jQuery-t, meg néhány másik libet is. Persze nem volt fenékig tejfel, jelentkeztek a szokásos legacy kódos problémák, pl. a logika és a view totál összenőtt. Ez persze még mind nem lett volna akkor nagy baj, csak a fejlesztés-feedback loop kicsit hosszabb volt a kelleténél:

  1. Js szerkesztése, speckuláció
  2. War forgatása, deploy
  3. Site tesztelése browserből

Akkor már tudtam, mi az a TDD, és azt is, hogy miért jó ez nekem (és a főnökömnek, a főnököm főnökének, a CEO-nak, a felhasználóknak, stb.)  Ezen a SO oldalon egészen sokat összeszedtek, szépen részletezték, hogy melyik miért jó/nem jó. Még azon is vitatkoztak, hogy jó-e browser-ből futtatni a unit teszteket. (Jó, mert élesben is ott fut. Rossz, mert komoly függőség a CI szervernek.) Rákattinthatnék bármelyikre, és le is tölthetném – otthonra.

A megoldás

A megoldás: addig maszíroztam a javascriptet, amíg jól nem működött. Ez van, szegény ember vízzel főz. Más szavakkal: az ügyfél örül, az igazi problémamegoldást viszont elhalasztottuk (és fizetgetjük a technikai törlesztőket).

Azóta időről időre elgondolkodom, mit tehetnék, amikor legközelebb ezen kell dolgoznom. Az egyik ötlete a RhinoUnit adta: Hiszen a Rhino Javascriptet tud futtatni java-ból! Sőt, java 6-tól fölfelé még külön jart se kell hozzá importálni, csak beírjuk, hogy

String javaScriptExpression = "helloWorld();";
Reader javaScriptFile = new StringReader(
    "function helloWorld() {\n"
        + "    println('Hello, World!');\n" + "}");
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");

engine.eval(javaScriptFile);
engine.eval(javaScriptExpression);

És kiírja, hogy “Hello, World!”. A fenti kódot amúgy ebből egyszerűsítettem.

Unit tesztek

Már csak köré kell rakni egy JUnit-ot, az ismétlődő részeket belerakjuk a setUp() -ba, és voila:

public class TestCalc {

	private ScriptEngine engine;

	@Before
	public void setUp() throws Exception {
		ScriptEngineManager factory = new ScriptEngineManager();
		engine = factory.getEngineByName("JavaScript");

		String[] sources = new String[] {
			"/com/tamasrev/Calc.js"
		};
		for (String src : sources) {
			Reader r = new InputStreamReader(getClass().getResourceAsStream(src));
			engine.eval(r);
		}
	}

	@Test
	public void testSum() throws Exception {
		//GIVEN
		String source = "sum(2, 3)";

		//WHEN
		Object result = engine.eval(source);

		//THEN
		assertEquals(5.0, result);
	}
}

Nézzük meg részletesebben, mi ez a sok betű?

A setUp metódusban összerakunk egy ScriptEngine-t és beletöltjük a teszt javascript dependenciáit. Azt gyanítom, hogy a ScriptEngineManager-t nem kell minden teszt metódushoz újra példányosítani; talán a ScriptEngine-t se. A build és a tesztek futtatása egyelőre tíz percen belül van, úgyhogy ez most még OK. Keretrendszernek rendesen ki kellene tesztelni a dolgokat.

Aztán a testSum() metódus összerak Stringben egy JavaScript hívást, lefuttatja, és a ScriptEngine által visszaadott értékre meghív egy JUnit-os assert-et. Mindez szép és jó, amíg tesztenként egy assertünk van. Van, aki szerint ne is legyen több. És van, aki szerint néha lehet. Szerintem meg mint minden szabály alól, ez alól is vannak kivételek. (Igen, ez is egy szabály volt).

Egyszerre több értéket ki tudunk nyerni pl JavaScript objektumként. Ez Java oldalon egy NativeObject, amit nagyjából, de tényleg csak nagyjából Map-ként kezelhetünk. Azaz:

	@Test
	public void testDividendAndRemainder() throws Exception {
		//GIVEN
		String source = "dividendWithRemainder(5, 2)";

		//WHEN
		Map result = (Map) engine.eval(source);

		//THEN
		assertEquals(2.0, result.get("dividend"));
		assertEquals(1.0, result.get("remainder"));
	}

Tehát Js-ből visszaadunk kulcs-érték párokat, és azokat Java-ban a szokásos módon asszertáljuk. A listák ellenőrzése házi feladat az olvasónak.

Miért

Miért jó ez nekünk? Azért, mert automatikusan futtathatunk unit teszteket Js-re. Nem kellenek hozzá csilli-villi hipszter libek. Sőt, még browser se kell hozzá. Elég a java 6, a jó öreg JUnit, és már mehet is.

Miért nem jó ez nekünk? Egyrészt, mert ezek nem unit tesztek: a legacy kódos könyvben Michael Feathers kifejti, hogy a unit tesztek gyorsak. Nem beszélnek a hálózaton, nem izélgetik a fájlrendszert, adatbázist végképp nem, hanem szépen izolációban futnak. Ami ezeket a szabályokat megszegi, az nem unit teszt. Persze attól még lehet hasznos, teszi hozzá.

Másrészt, ezek poliglott, többnyelvű tesztek, tehát pont elveszítjük az xUnit szépségét: hogy abban a nyelvben írjuk a tesztet, amiben a kódot is.

Összességében vegyes a kép. Ez van, szegény ember vízzel főz. Körbeértünk.

Hogyan tovább?

Ez itt egy proof of concept. Akit érdekel, itt megtalálja a teljes projektet. Ez még pont az a méret, amit bármelyik szegény ember be tud írni a belső hálós gépébe. Mindenki más használjon normális megoldásokat, például JsUnit-ot.

2 responses to “Szegény ember JsUnit-ja

  1. Király Péter augusztus 21, 2013 2:50 du.

    Viszonlag ritkán írok (és látok) sum() típusú funkciókat JavaScriptben, viszont annál gyakrabban egy HTML DOM fájának manipulációját. Gondolom a Rhinoval be lehet olvasni bármilyen JS keretrendszert, de mi a helyzet a HTML-lel, illetve a HTML-es események kezelésével? Vagy lehet, hogy írunk JS funkciókat a HTML fa létrehozására és események kiváltására, csak azért, hogy le tudjuk tesztelni az adott eseménykezelőt (vagy esetleg magában a Rhino-ban is vannak erre módszerek)?

    • tamasrev augusztus 21, 2013 10:48 du.

      Én viszonylag ritkán írok JavaScriptet, egyáltalán – és olyankor se férek hozzá az aktuális libekhez. Éppen ezért szöszölök ilyen szegényes megoldásokkal.

      A Rhinot még a Netscape kezdte el fejleszteni, ezért azt gondolom, tud HTML DOM-fát manipulálni. Más kérdés, hogy érdemes-e ezt TDD-zni.

      Szerintem nem érdemes, mert elég nehéz a UI kódot értelmesen tesztelni (azazhogy könnyítse a refaktorálást). Inkább szétvágnám a js kódot, mondjuk MVC mentén, aztán unit-tesztelném a modelt és a controllert.

      Teljes joggal kérdezhetnéd, hogy nem túlzás ez? Mégpedig azért, mert gyakran nem triviális,hogyan kell GUI kódot tesztelhetőre szétvágni. Ha ez sikerül, akkor megéri. Ha nem, akkor valami mást kell kitalálni.

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s

%d blogger ezt kedveli: