poniedziałek, 21 grudnia 2009

Testowanie pakunkow OSGI

Ostatni swój projekt realizuje w oparciu o platformę OSGI. Tym razem nie korzystałem z mavena, pax-* i tym podobnych narzędzi - skorzystałem z możliwości tworzenia pakunków jako projektów plug-in'ów eclipse (wskazując jako "Target Platform" Osgi framework zamiast Eclipse).

Utworzyłem pakunki, zacząłem je konfigurować i stwierdziłem że potrzebuję testów jednostkowych (zamiast sprawdzać czy aplikacja działa po prostu uruchamiając ją wolę pisać testy jednostkowe). I w tym miejscu pojawił się problem związany z platformą OSGI. Umieszczanie kodu testowego w tym samym projekcie w którym znajduje się kod aplikacji nie wydaje się ładnym rozwiązaniem. W dodatku moje pakunki byłyby zależne od bibliotek takich jak JUnit czy Mockito... Brrr... Naturalnym było więc utworzenie oddzielnych projektów zawierających testy dla każdego z pakunków. W ten sposób trafiłem z deszczu pod rynnę, gdyż umieszczając testy w oddzielnym pakunku mam dostęp jedynie do wyeksportowanych pakietów, czyli w przypadku gdy postępuję zgodnie ze sztuką (a postępuję ;) ) - samych interfejsów. W ten sposób testy jednostkowe przestają być jednostkowe. :|

Na pierwszy rzut oka sytuacja patowa. Na szczęście udało mi się znaleźć rozwiązanie. Polega ono na umieszczeniu kodu testowego w "Project Fragment" - specjalnym rodzaju projektu plug-in'u pozwalającego na jego dekompozycję. Tak więc mając projekt (typu "Plug-in Project") o nazwie abc zawierający pakunek tworzę nowy projekt (typu "Fragment Project") o nazwie abc.test wskazując jako Host Plug-in projekt abc. W ten sposób projekt abc.test posiada pełen dostęp do pakunku abc (włącznie z niewyeksportowanymi pakietami), a zarazem pozwala na wydzielenie testów (i ich zależności) z pakunku zawierającego logikę aplikacji.

środa, 9 grudnia 2009

Testowanie aplikacji Swing

Ostatnio postanowiłem uruchomić testy funkcjonalne dużej aplikacji napisanej w Swingu. Założenie było takie, aby testy naśladowały interakcję prawdziwego użytkownika.

Biblioteka Fest-Swing okazała się być idealna do tego celu. Co takiego umożliwia? Moim zdaniem dwie najważniejsze rzeczy to:

  • łatwe przeszukiwanie drzewa komponentów w celu znalezienia tego z którym chcemy podjąć interakcję

  • możliwość symulowania akcji użytkownika



Najpierw uruchamiamy aplikację:

Robot robot = BasicRobot.robotWithCurrentAwtHierarchy();
application("pakiet.aplikacji.Main").start();
FrameFixture mainFrame = findFrame(new GenericTypeMatcher(JFrame.class) {
protected boolean isMatching(JFrame frame) {
return frame.getTitle().startsWith("Tytul aplikacji")
&& frame.isShowing();
}
}).using(robot);


Najpierw tworzymy instancję klasy org.fest.swing.core.Robot - jest to wrapper na java.awt.Robot, gdyż to za jej pomocą odbywa się interakcja z aplikacją.
Następnie przy pomocy metody org.fest.swing.launcher.ApplicationLauncher.application tworzymy obiekt ApplicationLauncher jako parametr wskazując nazwę klasy, w której znajduje się metoda main naszej aplikacji, a następnie uruchamiamy go metodą start.

Następnie odnajdujemy obiekt klasy javax.swing.JFrame. Robimy to przy pomocy metody org.fest.swing.finder.WindowFinder.findFrame, jako argument przekazując instancję klasy org.fest.swing.core.GenericTypeMatcher, w której możemy określić dodatkowe warunki jakie ma spełniać obiekt JFrame.

Warto wspomnieć o klasach *Fixture - są to wrappery na standardowe elementy interfejsu graficznego takie jak przyciski, checkboxy, tabele, listy... to właśnie za ich pomocą możemy podejmować interakcję z komponentem (przykład to metoda click() w klasie ButtonFixture) lub wyszukiwać komponenty zawarte w kontenerze (przykladowo na obiekcie mainFrame możemy wywołać metode comboBox() w celu pobrania obiektu ComboboxFixture.

Jest tylko jedna uwaga: metoda wyszukująca obiekt musi odnaleść dokładnie jeden rezultat. W sytuacji, gdy kontener zabierałby więcej comboboxów, mysiałby zostać wybrany jeden z nich np. przy pomocy obiektu typu GenericTypeMatcher.

czwartek, 3 grudnia 2009

Zmiękczanie wyjatków za pomocą AspectJ

Kolejną ciekawą możliwością jaką daje AspectJ jest automatyczna zamiana twardych (checked) wyjątków na miękkie (unchecked) - rozszerzających klasę RuntimeException.

Czemu ma służyć taki manewr? Oczywiście wygodzie programisty. ;) Czasem zdarza się, że pomiędzy miejscem powstania wyjątku, a miejscem jego obsługi jest po drodze wywoływanych wiele metod. Teraz jeśli rzucanym wyjątkiem jest wyjątek twardy to każda metoda po drodze musi zadeklarować ze może ewentualnie taki wyjątek rzucić.


package wyjatki;

public class Wyjatki {
static void test() throws NoSuchMethodException{
Wyjatki.class.getMethod("abc", String.class);
}
static void test2() throws NoSuchMethodException{
test();
}
public static void main(String[] args) {
try {
test2();
} catch (NoSuchMethodException e) {
//obsluga wyjatku
e.printStackTrace();
}
}
}


Jeśli pisanie deklaracji throws jest dla nas uciążliwe możemy obejść problem opakowywując twardy wyjątek w jego miękki odpowiednik.


package wyjatki;

public class Wyjatki {
static void test(){
try{
Wyjatki.class.getMethod("abc", String.class);
}catch(NoSuchMethodException e){
throw new RuntimeException(e);
}
}
static void test2(){
test();
}
public static void main(String[] args) {
try {
test2();
} catch (RuntimeException e) {
Throwable e2 = e.getCause();
//obsluga wyjatku
e2.printStackTrace();
}
}
}


Kod w działaniu zachowuje się tak samo, ale metody test oraz test2 nie muszą deklarować NoSuchMethodException. W każdym razie programista musi ręcznie opakować wyjątek. I tutaj na scenę wkracza AspectJ. Posiada on deklarację declare soft, która odpowiada za opakowanie twardego wyjątku jego miękkim odpowiednikiem. Dla naszego przykładu wygląda ona następująco:


package wyjatki;

public aspect WyjatkiAspect {
declare soft : NoSuchMethodException : execution(void wyjatki.Wyjatki.test());
}


Natomiast klasa Wyjatki prezentuje się następująco:


package wyjatki;

public class Wyjatki {
static void test(){
Wyjatki.class.getMethod("abc", String.class);
}
static void test2(){
test();
}
public static void main(String[] args) {
try {
test2();
} catch (RuntimeException e) {
Throwable e2 = e.getCause();
//obsluga wyjatku
e2.printStackTrace();
}
}
}


Jak widać nie dość, że pozbyliśmy się deklaracji throws to jeszcze wyeliminowaliśmy opakowywanie NoSuchMethodException w metodzie test.

Na koniec dosyć ważna uwaga: twarde wyjątki po istnieją, aby były jawnie obsługiwane, dlatego ten mechanizm nie służy temu aby opakować wyjątek i o nim zapomnieć. Trzeba pamietać, aby go właściwie obsłużyć.

środa, 2 grudnia 2009

Static crosscutting w AspectJ

Każdy kto cokolwiek słyszał o AspectJ wie, że można za jego pomocą modyfikować wywołania metod (dynamic crosscutting), jednak mało kto wie, że za jego pomocą można także modyfikować statyczne właściwości klas, np dodawać atrybuty lub metody. To jest właśnie static crosscutting. Jak wykorzystać static crosscutting zaprezentuję na prostym przykładzie.

Tak więc mamy klasę reprezentująca wpis w serwisie blogowym.

package blog;

import java.util.ArrayList;
import java.util.List;

public class Wpis {
private String tresc;
private List komentarze = new ArrayList();
public Wpis() {
}
Wpis(String tresc){
this.tresc = tresc;
}
public String getTresc() {
return tresc;
}
public void setTresc(String tresc) {
this.tresc = tresc;
}
public List getKomentarze() {
return komentarze;
}
public void setKomentarze(List komentarze) {
this.komentarze = komentarze;
}
public void addKomentarz(String komentarz){
this.komentarze.add(komentarz);
}
@Override
public String toString() {
return "Wpis o tresci: " + tresc;
}
}


Dla celów demonstracyjnych utworzyliśmy także klasę Test demonstrującą użycie klasy Wpis.


package blog;

public class Test {
public static void main(String[] args) {
Wpis w = new Wpis("Hello world");
System.out.println(w.getTresc());
System.out.println(w.toString());
}
}


Załóżmy teraz, że chcemy dodać do niej atrybut reprezentujący datę utworzenia, automatycznie inicjowany bieżącym czasem.


package blog;

import java.text.MessageFormat;
import java.util.Date;

public aspect WpisAspect {
private Date blog.Wpis.dataUtworzenia;

public Date blog.Wpis.getDataUtworzenia(){
return this.dataUtworzenia;
}

after(Wpis wpis) returning() : execution(blog.Wpis.new(..)) && target(wpis) {
wpis.dataUtworzenia = new Date();
}

String around(Wpis wpis) : execution(public String blog.Wpis.toString()) && target(wpis) {
return MessageFormat.format("Wpis z dnia {0} o tresci: {1}", wpis.getDataUtworzenia(), wpis.getTresc());
}
}


Najpierw definiujemy nowy atrybut w klasie Wpis - jak widać nie różni się to od zwykłego utworzenia atrybutu, z wyjątkiem tego, że nazwa atrybutu zawiera też nazwę typu w którym będzie zdefiniowany. Następnie do klasy Wpis dodajemy metodę umożliwiającą pobranie wartości nowo-utworzonego atrybutu. Dokonujemy też modyfikacji wszystkich konstruktorów, tak by go odpowiednio inicjowały. Ostatnią modyfikacją jest "nadpisanie" metody toString() własną implementacją, która uwzględnia atrybut dataUtworzenia.

Oto rezultat uruchomienia klasy test:

Hello world
Wpis z dnia 02.12.09 19:33 o tresci: Hello world

Jak widać wszystko działa zgodnie z naszymi zamierzeniami.

Do czego można wykorzystać static crosscutting?

Pierwszą rzeczą jaka się nasuwa w odpowiedzi jest możliwość modyfikacji kodu do którego nie posiadamy źródeł. W takiej sytuacji możemy dokonać modyfikacji w kodzie bez nieetycznej dekompilacji.

Drugim, chyba sensowniejszym przykładem jego użycia będzie dodanie do klasy elementów, które chcemy mieć, ale które są typowo technicznymi jej składnikami, np logger, czy połączenie z bazą danych.

czwartek, 12 listopada 2009

Ejb dependency injection w JBoss AS

Pol dnia męczyłem się ze wstrzykiwaniem zaleznosci w Jboss AS.

Miałem bezstanowe ziarno ejb implementujące lokalny interface:


@Stateless(mappedName="ABC", name="ABC")
public class ABCService implements ABCLocal{
...
}


, które próbowałem wstrzyknąć do servletu używając


@EJB
ABCLocal abc;


Ponadto próbowałem ustawiać parametry adnotacji EJB

@EJB(beanName="ABC)
ABCLocal e1;
@EJB(beanName="ABC/local")
ABCLocal e2;
@EJB(mappedName="ABC")
ABCLocal e3;
@EJB(mappedName="ABC/local")
ABCLocal e4;
@EJB(name="ABC")
ABCLocal e5;
@EJB(name="ABC/local")
ABCLocal e6;


- każdorazowo wartością był null.

Zgodnie z dokumentacją JBossa ziarna są rejestrowane w JNDI pod nazwa_ear/nazwa_ejb/local oraz nazwa_ear/nazwa_ejb/remote. Niestety to rozwiązanie nie odpowiadało mi, gdyż nie chciałem uzależniać działania aplikacji od nazwy archiwum ear.

Ostatecznie zdecydowałem sie na dodanie do ziarna adnotacji


@LocalBinding(jndiBinding = "ABCApp/ABC/local")


dzięki czemu mogę sie odwoływać do ziarna za pomocą


@EJB(mappedName="ABCApp/ABC/local")
ABCLocal abc;

niedziela, 18 października 2009

UML w XPand project

Podczas próby użycia UMLowego modelu w projekcie Xpand otrzymałem błąd:

SEVERE: org.eclipse.emf.ecore.xmi.PackageNotFoundException: Package with uri 'http://www.eclipse.org/uml2/3.0.0/UML' not found.


Rozwiązaniem jest następująca modyfikacja definicji workspace:

<bean class="org.eclipse.emf.mwe.utils.StandaloneSetup" >
<platformUri value=".."/>
<registerGeneratedEPackage value="org.eclipse.uml2.uml.UMLPackage"/>
</bean>


oraz dodanie
org.eclipse.uml2.uml,
org.eclipse.uml2.common

w sekcji Require-Bundle w MANIFEST.MF

Błąd w Xpand Project

Po integracji openarchitectureware w ramach projektu eclipse pojawił się błąd w działaniu kreatora tworzącego nowy projekt. Błąd objawia się logiem:

"SEVERE: [ERROR]: Class not found: 'org.eclipse.mwe.emf.StandaloneSetup'"


Rozwiazanie z forum eclipse:

Otworzyc plik META-INF/MANIFEST.MF, sekcje "Require-Bundle" wyedytowac nastepujaco:

Require-Bundle: org.eclipse.xpand,
org.eclipse.xtend,
org.eclipse.xtend.typesystem.emf,
org.eclipse.emf.mwe.utils;bundle-version="0.7.2",
org.eclipse.emf.ecore.xmi;bundle-version="2.5.0",
org.eclipse.jface.text;bundle-version="3.5.0",
org.antlr.runtime;bundle-version="3.0.0",
com.ibm.icu;bundle-version="4.0.1",
org.eclipse.core.runtime;bundle-version="3.5.0",
org.eclipse.jdt.core;bundle-version="3.5.0"


A następnie zastąpić zawartość pliku generator.mwe na poniższą:

<?xml version="1.0"?>
<workflow>
<property name="model" value="my.generator.projectxxx/src/Model.xmi" />
<property name="src-gen" value="src-gen" />

<!-- set up EMF for standalone execution -->
<bean class="org.eclipse.emf.mwe.utils.StandaloneSetup" >
<platformUri value=".."/>
</bean>

<!-- load model and store it in slot 'model' -->
<component class="org.eclipse.emf.mwe.utils.Reader">
<uri value="platform:/resource/${model}" />
<modelSlot value="model" />
</component>

<!-- check model -->
<component class="org.eclipse.xtend.check.CheckComponent">
<metaModel id="mm"
class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel"/>
<checkFile value="metamodel::Checks" />
<emfAllChildrenSlot value="model" />
</component>

<!-- generate code -->
<component class="org.eclipse.xpand2.Generator">
<metaModel idRef="mm"/>
<expand
value="template::Template::main FOR model" />
<outlet path="${src-gen}" >
<postprocessor class="org.eclipse.xpand2.output.JavaBeautifier" />
</outlet>
</component>
</workflow>

sobota, 4 kwietnia 2009

Pomocnicze narzędzia Portage

qfile wyświetla z jakiego pakietu jest dany plik:

# qfile /usr/bin/bzr
dev-util/bzr (/usr/bin/bzr)

qlist wyświetla pliki z danego pakietu

# qlist bzrtools
/usr/share/doc/bzrtools-1.9.1/TODO.Shelf.bz2
/usr/share/doc/bzrtools-1.9.1/README.bz2
/usr/share/doc/bzrtools-1.9.1/TODO.bz2
(...)

piątek, 3 kwietnia 2009

Bazaar jako rozproszone repozytorium

Rozproszona praca w Bazaar w skrócie polega na tworzeniu kopii [M] (mirrorow) głównego repozytorium [G] (każdy programista powinien utworzyć własną lokalną kopię repozytorium). Następnie tworzone są robocze wersje [W] gałęzi [M] w których dokonujemy zmian. Cała reszta to jedynie łączenie (merge) poszczególnych gałęzi.

Pierwszym krokiem jest utworzenie mirror'a głównego repozytorium:

bzr branch [url_glownego_repozytorium]/projekt


Następnie tworzymy gałąź (jedną lub więcej) dla naszych zmian

bzr branch projekt roboczy


W przypadku pojawienia się nowych zmian w repozytorium głównym uaktualniamy nasz mirror poleceniem

bzr pull


wykonanym oczywiście w katalogu mirror'a.

Katalog roboczy względem mirror'a uaktualniamy przy pomocy merge (polecenie jest wywołane z katalogu roboczy):

bzr merge


Jeśli występują konflikty to oczywiście musimy je rozwiązać, a następnie zapisujemy zmiany do repozytorium roboczego

bzr commit


Jeżeli teraz chcemy przenieść zmiany z katalogu roboczego do repozytorium głównego to przechodzimy do katalogu z kopią repozytorium głównego (mirrorem), uaktualniamy ją (względem repozytorium głównego) poleceniem:

bzr pull


,a następnie scalamy z wersją roboczą:

bzr merge [scieżka do wersji roboczej]


Po rozwiązaniu ewentualnych konfliktów zapisujemy zmiany do repozytorium mirror'a i wysyłamy do repozytorium głównego:

bzr commit
bzr push

czwartek, 2 kwietnia 2009

Tworzenie projektu w zdalnym repozytorium bazaar

Warunek poczatkowy: mamy działający serwer ftp majacy okreslony adres, port, a także uzytkownika z przypisanym haslem i prawem do zapisu do sciezki.

Pierwszym krokiem jest przedstawienie sie systemowi bazaar:

bzr whoami "Nick "


Poprawnosc operacji możemy sprawdzić poleceniem

bzr whoami


Następnie tworzymy katalog projektu i pierwsze pliki w nim

mkdir projekt
cd projekt
touch file1 file2 file3


Za pomoca polecenia

bzr init


tworzymy lokalne repozytorium w bieżącym katalogu. Bazaar utworzy katalog .bzr.

Następnie wyrażamy chęć dodania wszystkich plików (add bez parametrow) projektu podczas najblizszego commit do repozytorium oraz wykonujemy sam commit, podajac przy tym opis zmiany.

bzr add
bzr commit -m "Import projektu"


Nastepnie poleceniem

bzr push --create-prefix ftp://uzytkownik@adres:port/sciezka/projekt


Przenosimy repozytorium na serwer ftp.

Wlasna gałąź źródeł projektu możemy zawsze pobrać poleceniem:

bzr branch ftp://uzytkownik@adres:port/sciezka/projekt


Mozemy rozpoczynać pracę.