25. října 2007

Testovací frameworky pro webové GUI

Rozhodli jsme se, že posuneme naše interní testování aplikací zase o kousek dále, a že vybereme nějaký testovací framework pro GUI webových aplikací.

Zatím jedinou zkušenost, co se týká testování web GUI, jsem měl s jWebUnit. Již jsem tuto knihovnu delší dobu nepoužíval, ale myslím si, že by se v dnešní době dalo najít lepší řešení. Ono je to tedy těžké napsat "lepší řešení", protože každý má jiné požadavky a cíle testování - někdo upřednostní GUI klienta na vytváření testů, někdo má rád programové vytváření testů přes API, někdo chce zaměřit na testování kompatibility mezi prohlížeči atd. Přesněji řečeno se tedy snažím "zmapovat trh" s testovacími frameworky a jeden vybrat a ten zkusit.

Já jsem věnoval zhruba půl dne tomu, abych si udělal hrubý obrázek o současných možnostech testovacích frameworků pro web GUI. V rychlosti jsem prostudoval Canoo WebTest, Selenium a Jameleon - ty jsem nominoval do užšího výběru na základě referencí od známých nebo podle toho, co jsem se dočetl na webu.
Znovu chci zdůraznit, že jsem vše procházel velice hrubě - pročetl jsem si základní dokumentaci, vyzkoušel dostupné nástroje a napsal 1-2 zkušební testíky. Nic víc. Pokud se mnou v nějakém bodě hodnocení nebudete souhlasit nebo naopak by jste něco doplnili, tak budu jedině rád.

Pro úplnost ještě uvádím odkaz na přehled všech možných testovacích knihoven a frameworků pro Javu.

Canoo WebTest

Plusy:
  • testovaní bez nutnosti použití webového prohlížeče

  • ovládání pomocí ANTu - Canoo definuje ANT tasky, kterými je možné vše ovládat (hlavně spouštět testy)

  • možnosti kontroly PDF a Excel dokumentů

  • dokumentace je rozsáhlá a přehledná

  • velice intuitivní, testy může vytvářet i neprogramátor

Mínusy:
  • vše je nutné definovat ručně přímo v XML, žádné podpůrné grafické rozhraní

  • Canoo nesimuluje reálného uživatele přes prohlížeč, pouze se víceméně zajímá o čisté HTML (to může být i výhoda pro určité typy testů)

  • může být rozdílné chování z pohledu Canoo a reálného uživatele (např. různá implementace Javascriptu v prohlížeči a v této knihovně)



Selenium

Plusy:
  • simuluje reálného uživatele

  • možnost testování kompatibility webové aplikace s prohlížeči

  • Selenium IDE (plugin do Firefox) - přes toto IDE je možné vytvářet testy (manuálně nebo nahráváním); velice intuitivní nástroj, vhodný i pro neprogramátory. Tento plugin z mého pohledu nemá chybu, parádní integrace s Firefoxem.

  • testy je možné vytvářet nahráváním pomocí Selenium IDE

  • možnost rozšiřování základní funkcionality pomocí extensions

  • ANT tásky a možnost spouštění testů z příkazové řádky

  • dokumentace, podpora

  • možnost vytváření testů přímo z jazyka Java případně z jiných jazyků (.NET, PHP, Perl, ...)

Mínusy:
  • měl jsem malé problémy se spouštěním testů z příkazové řádky, které jsem nejdříve vytvořit přes IDE nahráním

  • testovaná aplikace musí být spuštěna před vlastním testováním



Jameleon

Plusy:
  • asi nejvíce rozsáhlé a robustní řešení na testování. Je možné využívat formou pluginů jiné webové testovací frameworky - Selenium, jWebUnit, ...

  • není jen pro testovaní web GUI, ale obecně pro testování čehokoliv

Mínusy:
  • asi nejstrmější "learning-curve" z uvedených frameworků. U předchozích jsem měl hned pocit, že vím, jak s tím pracovat, tady to tak rychlé nebylo.

  • poslední release byl v prosinci 2006, od té doby nic nového (navíc poslední uvolněná verze není stabilní). Odkazy na archiv mailing listu hlásí chybu.

  • mam pocit, ze Jameleon je základní framework pro testování, který je potřeba si ještě rozšířit pro své konkrétní potřeby, aby se jeho potenciál maximálně využil (toto ale může být i výhoda)


Vítězem prvního kola se u mě stal framework Selenium. Rozhodla jednoduchost vytváření testů (která ale není vyvážena tím, že by to bylo slabé na funkcionalitu) a možnost vytváření testů neprogramátory.

20. října 2007

ImageUploader - komponenta pro upload souborů

Na posledním projektu jsme celkem dost intenzivně používali komponentu ImageUploader od firmy Aurigma. Tuto nebo jinou komponentu jsme museli použít, protože zákazník měl následující požadavky:

  • elegantní způsob výběru dokumentů (textových i obrazových)

  • možnost náhledů pro obrazové dokumenty

Také je vhodné dodat, že předchůdce námi vyvíjené aplikace byla desktopová aplikace a spoustu uživatelů nebylo možné přesvědčit, že webová aplikace se chová jinak než standardní Windows aplikace.



S komponentou jsme pracovali přibližně půl roku. Komponentu jsme nevyužívali jen pro jednoduché odesílání souborů, ale potřebovali jsme odeslat dalších cca 30 položek formuláře. Některé věci šli celkem bez problémů, jiné bylo potřeba různými způsoby obcházet a myslím, že s výsledkem je i zákazník spokojený.
Než abych to složitě popisoval, tak nyní shrnu výhody a nevýhody, které tato komponenta z našeho pohledu má.

Výhody
  • komponenta je dostupná jak pro IE (ActiveX verze), tak i pro ostatní prohlížeče (Java applet verze)

  • kvalitní dokumentace včetně kvalitního online dema

  • celkem kvalitní podpora, např. forum

  • chování komponenty lze upravit konfigurací velkého množství parametrů, vše se ovládá pomoci JavaScriptu

  • komponenta neslouží jen k výběru souborů a jejich odeslání, ale umožňuje například vytváření náhledů, získávání informací z EXIF, přidání vodoznaku atd. Přehled vlastností je možné si prohlédnout zde.

  • i cena mi přijde rozumná. Za dualní verzi (ActiveX a Applet) s vazbou na doménové jméno serveru je cena $299.


Nevýhody
  • možnosti a chování obou verzí (ActiveX, Applet) nejsou totožné. Není to jen rozdíl (není ale jinak výrazný) v chování komponenty z pohledu uživatelského ovládání, ale hlavně rozdílu z pohledu funkcionality - odlišná podpora formátů obrázků (např. TIFF obrázky), jiné možnosti získání informací z EXIF, odlišný přístup pro získání data vzniku dokumentu. Toto je zřejmě dáno rozdíly v použitých jazycích pro vývoj těchto komponent (Java vs. C#).

  • slabá integrace s komponentou Thumbnail. Tato komponenta je dodávaná spolu s ImageUploader a slouží pro zobrazování náhledů vybraných dokumentů. Tato integrace ale nefunguje úplně 100% - je k tomu potřeba celkem dost JavaScript kódu, chování se liší pro obě verze komponenty a pořád se nám to nepodařilo dotáhnout do takové podoby, jakou by jsme chtěli.

  • zatímco ActiveX komponenta resp. IE neměly s testovacími certifikáty (tj. certifikáty podepsanými naší interní autoritou) žádné problémy, u Java verze to bylo horší. Pokud jsou ale certifikáty pro HTTPS podepsané nějakou známou autoritou, tak také není žádný problém.


I přes nějaké ty nevýhody jsem byl spokojený a pokud bude potřeba, tak tuto komponentu použiji i v dalších projektech.

18. října 2007

Zajímavé články o Acegi security

Dnes jsem narazil na (z mého pohledu) velice zajímavé články o Acegi. Kdyby podobné články byly již před pár lety, kdy jsem začínal s Acegi, tak bych si určitě ušetřil spoustu času :).

Acegi Security in one hour

Securing Java applications with Acegi


From Java EE security to Acegi

15. října 2007

Vytváření JSP EL funkcí

JSP EL používám často a sem tam se mi stane, že bych potřeboval použít vlastní funkci. J2EE specifikace nabízí velmi elegantní řešení, jak si rychle takovou funkci vytvořit.
Pozn. Přijde mi, že je to v tutoriálu J2EE trochu zapadlé, tak proto jsem se rozhodl o tom napsat dnešní článek.

Originální popis se nachází zde v tutoriálu J2EE.

Pro demonstraci postupu jsem si vybral funkci, která mi řekne, zda je/není daný prvek v kolekci.

Vytvoření funkce

package cz.anect.mis.web.tags;

import java.util.Collection;

/**
* This class encapsulates handy funcions for using in JSP EL.
*
* Functions must be defined in /WEB-INF/functions.tld.
*
* @author pjuza@anect.com
*/
public class Functions {

/**
* Method returns true if specified object is contained in
* input collection.
* @param col Input collection for searching
* @param obj Searched object
* @return returns true if specified object is contained in input collection.
*/
public static boolean inCollection(Collection col, Object obj) {
if (col == null || obj == null)
return false;

return col.contains(obj);
}
}

Snad jediné omezení je, že funkce musí být statická a veřejná (public).

Definice funkce pomocí TLD

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>A tag library that defines my custom functions.</description>
<tlib-version>1.0</tlib-version>
<short-name>FunctionTagLibrary</short-name>
<uri>/FunctionLibrary</uri>

<function>
<name>inCollection</name>
<function-class>cz.anect.mis.web.tags.Functions</function-class>
<function-signature>boolean inCollection(java.util.Collection, java.lang.Object)</function-signature>
</function>
</taglib>


Použití funkce

Nejdříve musíme TLD standardně nadeklarovat před vlastním použitím na stránce:
<%@ taglib uri="/WEB-INF/tld/functions.tld" prefix="myfn" %>

A nyní můžeme bez problémů danou funkci použít:

<c:forEach var="kw" items="${keywords}">
<c:if test='${not myfn:inCollection(search.basicKeyWordIds, kw.id)}'>
<option value="<c:out value="${kw.id}"/>"><c:out value="${kw.value}"/></option>
</c:if>
</c:forEach>

5. října 2007

AOP proxy objekty - volání metody v rámci jedné třídy

Dlouho jsem přemýšlel nad nějakým výstižným názvem tohoto příspěvku, ale nic moc jsem nevymyslel. Zkusím tedy popsat o co přesně jde.
Mám třídu, která má dvě metody. Obě metody se mají pouštět v transakci, každá metoda může nebo nemusí mít svoje vlastní nastavení transakční definice. Pro účely ukázky jsem si vymyslel následující příklad:

public interface DocumentFacade {
/**
* Metoda pridava vice dokumentu.
*/
public DocumentIdsFacadeResult addDocuments(DocumentsDTO doc);

/**
* Metoda pridava pouze jeden dokument, tato metoda je volana metodou addDocuments().
*/
public DocumentIdsFacadeResult addDocument(DocumentsDTO doc);
}

<bean id="DbTransactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="dbTransactionManager" />
<property name="transactionAttributes">
<props>
<prop key="add*">PROPAGATION_REQUIRED,-Exception</prop>
</props>
</property>
</bean>

<bean id="serviceProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<idref bean="documentFacade" />
</list>
</property>
<property name="interceptorNames">
<list>
<idref bean="DbTransactionInterceptor" />
</list>
</property>
</bean>

Z požadavků zákazníka vyplývá následující chování - pokud se bude vkládat více dokumentů, tak výsledek každého vložení dokumentu bude nezávislý (atomický) od ostatních vložení dokumentů. Když tedy budu přidávat dva dokumenty a během prvního se objeví nějaká chyba ve zpracování, tak vložení druhého dokumentu to nemůže ovlivnit. Jinak řečeno každé volání metody addDocument() bude ve své vlastní transakci.

Pro méně zkušené uživatele AOP (mezi ně se řadím i já) se může zdát, že předcházející požadavek je automaticky vyřešen nastavením transakcí - obě metody odpovídají nastaveným transakčním atributům (viz bean serviceProxyCreator). A zde je právě kámen úrazu. Pokud bychom volali jednotlivé metody nezávisle (např. z kontroleru), tak vše bude chodit bez problémů, ale vzhledem k tomu, že jedna metoda je volána z druhé metody stejné třídy resp. objektu, tak nedochází ke vstupu do objektu přes proxy (jsme pořád v tom samém objektu) a tudíž se neaktivuje transakční nastavení pro druhou metodu. Pro náš příklad to znamená, že vše se provede pouze v jedné transakci definované pro první metodu, tedy addDocuments().
Toto samozřejmě není problém jen transakcí, ale obecně všech AOP proxy objektů.

Jaká jsou možná řešení? Z dostupných informací jsem našel následující řešení:
  • Refaktorovat strukturu volání, tak aby nedocházelo k volání v rámci jedné třídy. Toto řešení se může nakonec ukázat jako nejvíce použitelné pro případy, které nelze vyřešit následujícím řešením.

  • Použít programové transakční vymezení pomocí TransactionTemplate. Toto je celkem elegantní řešení pokud nechceme "uměle" refaktorovat kód. Problém může ovšem být v případě, kdy potřebujeme nastavit transakci přes více zdrojů (např. databáze a JCR). Pro tento případ je pak nutné použít JtaTransactionManager.
    Možná by šla ještě vnořená implementace TransactionTemplate, ale toto jsem nezkoušel. Velká výhoda nastavení transakcí ve Springu je ta, že infrastrukturní věci jako transakce mohu definovat deklarativně a mimo vlastní kód - zde tyto výhody ztrácím.

  • Programově se dostat k proxy objektu a uměle nasimulovat volání metody z vně objektu. Přesně tomuto řešení nerozumím (více informací je možné nalézt zde), ale každopádně je to už řešení, které je pevně spojeno se Springem a vlastním kódem.


Já osobně jsem zatím vždy použil druhý způsob.