Es wird die Enterprise Application "KuchenZutatNM" aus dem KuchenZutatNM-Beispiel verwendet. Dies wird in Eclipse importiert.
Am Code des EJB-Projekts sind folgende Änderungen vorzunehmen:
Ein Remote-Interface für den KuchenZutatNMWorker
muss zugefügt werden:
@Remote()
public interface KuchenZutatNMWorker
{
public void saveKuchen(KuchenNMBean kuchen);
public List<KuchenNMBean> getKuchen();
public KuchenNMBean findKuchenById(Integer int_Id);
public void deleteKuchen(KuchenNMBean kuchen);
public void addZutatToKuchen (KuchenNMBean kuchen, ZutatNMBean zutat);
public void removeZutatFromKuchen (KuchenNMBean kuchen, ZutatNMBean zutat);
public ZutatNMBean findZutatById(Integer int_Id);
public void deleteZutat(ZutatNMBean zutat);
public void saveZutat(ZutatNMBean zutat);
public List<ZutatNMBean> getZutaten();
}
Die Bean muss natürlich auch dieses Interface implementieren:
@Stateless
public class KuchenZutatNMWorkerBean implements KuchenZutatNMWorkerLocal, KuchenZutatNMWorker
{
Wir fügen der Enterprise Application ein Application Client-Projekt zu:
Das Projekt soll "KuchenZutatNMTestClient" heißen. Wichtig ist dass wir es zur Enterprise Application "KuchenZutatNM"
zufügen:
Alle weiteren Einstellungen können wir auf dem Default lassen.
Wichtig ist, dass wir uns im letzten Schritt den Deployment-Deskriptor "application-client.xml" erzeugen lassen.
Das Projekt muss in den "J2EE Module Dependencies" das EJB-Modul referenzieren.
Jetzt fügen wir eine Referenz auf das Remote Interface der "KuchenZutatNMWorkerBean" zu:
"application-client.xml":
<?xml version="1.0" encoding="UTF-8"?>
<application-client id="Application-client_ID" version="5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_5.xsd">
<display-name>
KuchenZutatNMTestClient</display-name>
<ejb-ref>
<ejb-ref-name>ejb/KuchenZutatNMWorker</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<!-- A senseless "home" item is needed, otherwises JBoss would complain...-->
<home>java.lang.Object</home>
<remote>de.fhw.swtvertiefung.knauf.kuchenzutatnm.KuchenZutatNMWorker</remote>
</ejb-ref>
</application-client>
"jboss-client.xml":
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-client PUBLIC "-//JBoss//DTD Application Client 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss-client_4_0.dtd" >
<jboss-client>
<jndi-name>KuchenZutatNMTestClient</jndi-name>
<ejb-ref>
<ejb-ref-name>ejb/KuchenZutatNMWorker</ejb-ref-name>
<jndi-name>KuchenZutatNM/KuchenZutatNMWorkerBean/remote</jndi-name>
</ejb-ref>
</jboss-client>
Wir legen einen Test im Application Client an: Rechtsklick, "New", "Other...", in der Rubrik "Java" - "JUnit" finden wir
den "JUnit Test case".
Wichtig ist dass wir einen JUnit-4-Test zufügen. Wir lassen uns die Methoden "setUp" und "tearDown"
generieren (auch wenn diese erst in einem späteren Test wirklich Verwendung finden werden !).
Wir fügen eine public Methode "testKuchen" zu. Dies wird mit der Annotation org.junit.Test
versehen.
@Test
public void testKuchen() throws Exception
{
Object objRemote = this.getInitialContext().lookup("java:comp/env/ejb/KuchenZutatNMWorker");
KuchenZutatNMWorker kuchenZutatWorker = (KuchenZutatNMWorker) PortableRemoteObject.narrow(objRemote, KuchenZutatNMWorker.class);
KuchenNMBean kuchenNeu = new KuchenNMBean ();
kuchenNeu.setName(KuchenZutatTestUtil.KUCHENNAME1);
kuchenZutatWorker.saveKuchen(kuchenNeu);
//Laden. Geht hier nicht über ID !
KuchenNMBean kuchenLoad = KuchenZutatTestUtil.getKuchenByName(kuchenZutatWorker, KuchenZutatTestUtil.KUCHENNAME1);
assertNotNull("Kuchen " + KuchenZutatTestUtil.KUCHENNAME1 + " nicht gefunden !", kuchenLoad);
//Löschen:
kuchenZutatWorker.deleteKuchen(kuchenLoad.getId() );
//Prüfen dass wir den nicht mehr finden...
kuchenLoad = KuchenZutatTestUtil.getKuchenByName(kuchenZutatWorker, KuchenZutatTestUtil.KUCHENNAME1);
assertNull("Kuchen " + KuchenZutatTestUtil.KUCHENNAME1 + " nach Löschen gefunden !", kuchenLoad);
}
Die Methode getInitialContext
ist eine private Hilfsmethode die den InitialContext liefert.
Die Methoden der Klasse org.junit.Assert
bietet uns diverse Möglichkeiten
um eine Bedingung abzuprüfen und im Fehlerfall den Test mit einer entsprechenden Meldung als "Fehlgeschlagen" zu deklarieren.
Hier sehen wir ein neues Feature von Java5: eigentlich müsste der Aufruf von assertNotNull
so erfolgen: Assert.assertNotNull
.
Java5 erlaubt es allerdings static Methoden einer Klasse wie ein Package zu importieren: import static org.junit.Assert.*;
Unser obiger Test enthält eine potentielle Fehlerquelle: wenn bereits ein Kuchen mit dem Namen KuchenZutatTestUtil.KUCHENNAME1
vorhanden wäre, dann würde der Zugriff auf den Kuchen nach dem Löschen trotzdem erfolgreich sein. Eine Lösung
wäre im "setUp" bzw. "tearDown" dafür zu sorgen dass die Datenbank explizit geleert wird.
Das Beispiel enthält einen identisch aussehenden Test für die Zutat-Bean.
Ein komplexerer Test ist "TestKuchenZutat":
public class TestKuchenZutat
{
/**Der Worker für den Test. Wird im "setUp" geholt. */
private KuchenZutatNMWorker kuchenZutatWorker = null;
/**Mit diesen Kuchen wird gearbeitet. */
private Integer intKuchenId1 = null, intKuchenId2 = null;
/**Mit diesen Zutaten wird gearbeitet. */
private Integer intZutatId1 = null, intZutatId2 = null;
public TestKuchenZutat(String name)
{
super(name);
}
@Before
public void setUp() throws Exception
{
//Worker holen:
Object objRemote = this.getInitialContext().lookup("java:comp/env/ejb/KuchenZutatNMWorker");
this.kuchenZutatWorker = (KuchenZutatNMWorker) PortableRemoteObject.narrow(objRemote, KuchenZutatNMWorker.class);
//Zwei Kuchen und zwei Zutaten anlegen:
KuchenNMBean kuchenNeu = new KuchenNMBean ();
kuchenNeu.setName(KuchenZutatTestUtil.KUCHENNAME1);
kuchenZutatWorker.saveKuchen(kuchenNeu);
//Direkt wieder rausholen:
//TODO Hier erfolgt keinerlei Prüfung !
this.intKuchenId1 = KuchenZutatTestUtil.getKuchenByName(this.kuchenZutatWorker, KuchenZutatTestUtil.KUCHENNAME1).getId();
kuchenNeu = new KuchenNMBean ();
kuchenNeu.setName(KuchenZutatTestUtil.KUCHENNAME2);
kuchenZutatWorker.saveKuchen(kuchenNeu);
this.intKuchenId2 = KuchenZutatTestUtil.getKuchenByName(this.kuchenZutatWorker, KuchenZutatTestUtil.KUCHENNAME2).getId();
ZutatNMBean zutatNeu = new ZutatNMBean ();
zutatNeu.setZutatName(KuchenZutatTestUtil.ZUTATNAME1);
kuchenZutatWorker.saveZutat(zutatNeu);
this.intZutatId1 = KuchenZutatTestUtil.getZutatByName(this.kuchenZutatWorker, KuchenZutatTestUtil.ZUTATNAME1).getId();
zutatNeu = new ZutatNMBean ();
zutatNeu.setZutatName(KuchenZutatTestUtil.ZUTATNAME2);
kuchenZutatWorker.saveZutat(zutatNeu);
this.intZutatId2 = KuchenZutatTestUtil.getZutatByName(this.kuchenZutatWorker, KuchenZutatTestUtil.ZUTATNAME2).getId();
}
@After
public void tearDown() throws Exception
{
//Kuchen und Zutaten löschen:
this.kuchenZutatWorker.deleteKuchen( this.intKuchenId1);
this.kuchenZutatWorker.deleteKuchen( this.intKuchenId2);
this.kuchenZutatWorker.deleteZutat( this.intZutatId1);
this.kuchenZutatWorker.deleteZutat( this.intZutatId2);
//Worker wegwerfen:
this.kuchenZutatWorker = null;
}
private InitialContext getInitialContext() throws Exception
{
...Implementierung wie im TestKuchen
}
@Test
public void testZutat() throws Exception
{
//Kuchen1 als echtes Objekt holen. Geht hier nicht über ID !
KuchenNMBean kuchenLoad = this.kuchenZutatWorker.findKuchenById(this.intKuchenId1);
assertNotNull("Kuchen " + KuchenZutatTestUtil.KUCHENNAME1 + " nicht gefunden !", kuchenLoad);
//Zutat 1 und 2 holen:
ZutatNMBean zutat1 = this.kuchenZutatWorker.findZutatById(this.intZutatId1);
assertNotNull("Zutat mit ID " + this.intZutatId1 + " nicht gefunden !", zutat1);
ZutatNMBean zutat2 = this.kuchenZutatWorker.findZutatById(this.intZutatId2);
assertNotNull("Zutat mit ID " + this.intZutatId2 + " nicht gefunden !", zutat2);
//In den Kuchen hängen:
kuchenZutatWorker.addZutatToKuchen(kuchenLoad.getId(), zutat1.getId());
kuchenZutatWorker.addZutatToKuchen(kuchenLoad.getId(), zutat2.getId());
//Den Kuchen erneut holen und sicherstellen dass er zwei Zutaten hat:
kuchenLoad = this.kuchenZutatWorker.findKuchenById(this.intKuchenId1);
assertNotNull("Kuchen mit ID " + this.intKuchenId1 + " nicht gefunden !", kuchenLoad);
assertTrue("Kuchen hat keine zwei Zutaten", kuchenLoad.getZutaten().size() == 2);
//Jetzt die Zutat 2 löschen. Die muss ich erstmal als echtes Objekt suchen.
zutat1 = this.kuchenZutatWorker.findZutatById(this.intZutatId1);
assertNotNull("Zutat mit ID " + this.intZutatId1 + " nicht gefunden !", zutat1);
this.kuchenZutatWorker.removeZutatFromKuchen(kuchenLoad.getId(), zutat1.getId());
//Der Kuchen darf jetzt nur noch eine Zutat haben:
kuchenLoad = this.kuchenZutatWorker.findKuchenById(this.intKuchenId1);
assertNotNull("Kuchen mit ID " + this.intKuchenId1 + " nicht gefunden !", kuchenLoad);
assertTrue("Kuchen hat nicht genau eine Zutat", kuchenLoad.getZutaten().size() == 1);
}
}
Hier sieht man die Verwendung von "setUp" / "tearDown": die Methoden werden benutzt um die Datenbank mit einem
definierten Datenbestand zu initialisieren.
Es fällt auf dass alle vier Tests die Methode "getInitialContext" enthalten. Aus diesem Grund wurde eine gemeinsame Basisklasse TestBase
erstellt, die den InitialContext in einer Membervariablen hält und ihn beim ersten Aufruf einer protected
Methode getInitialContext
anlegt.
public class TestBase
{
private InitialContext initialContext = null;
protected InitialContext getInitialContext() throws Exception
{
//InitialContext nur erzeugen beim ersten Abruf:
if (this.initialContext == null)
{
Properties props = new Properties();
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming.client");
props.setProperty(Context.PROVIDER_URL, "jnp://localhost:1099");
props.setProperty("j2ee.clientName", "KuchenZutatNMTestClient");
this.initialContext = new InitialContext(props);
}
return this.initialContext;
}
}
Die einzelnen Tests sind Subklassen hiervon:
public class TestKuchenZutat extends TestBase
{
...