Java 2 EE
- jUnit
- Client-zugriff
- Proxies im Container
- InitialContextFactory
- NameNotFoundException
- NamingException
- RemoteException
J2EE mit Spring
- war-Deployment
- spring-test
- Deployment mit Mocks
- Orchestrierung mittels spring.xmls
- Projektspezifische "Frameworks"
Java EE ohne Spring
... ohne Springtest
Historie von Arquillian
- Entstand bei der Implementierung des JSR 299 (CDI)
- Weiterentwicklung durch JBoss
- Fokus: Test ihrer Applikationsserver und Frameworks
- Wer entwickelt Applikationsserver?
- Wer entwickelt Java EE Frameworks?
Ziele von Arquillian
- Tests unter realen Bedingungen
- Unstützung der relevanten Szenarien
Features
- Steuerung des Container–Lebenszyklus
- Erstellung von Artefakten
- Installieren von Artefakten
- Bereitstellung von Komponenten in den Test
- Plug-in-Konzept für Erweiterungen
Client
Server
Remote
- Verwendung einer bestehenden laufenden Instanz
- Am schnellsten
- Ergebnis realitätsnah
- Manuelles Setup
- Manuelles Starten/Stoppen
- Hängenbleiben von Deployments
Managed
- Verwendung einer bestehenden Installation
- Start/Stop durch Arquillian
- Ergebnis realitätsnah
- Automatisches Starten/Stoppen
- Längere Laufzeiten
- Manuelles Setup
- Hängenbleiben von Deployments
Embedded
- Verwendung eines embedded-Containers in der Test-JVM
- Kein Hängenbleiben von Deployments
- Automatisches Setup
- Automatisches Startup
- Längere Laufzeiten
- Ergebnis ggf. nicht vollständig realistisch
- Konflikte von Bibliotheken (z.B. guava)
Debugging
- Embedded: Alles
- Client: Nur den Test
- Ansonsten: Remote-Debugging
Bestandteile
- arquillian-core
- arquillian Testadapter (junit, testng, ...)
- arquillian Containeradapter
- weld
- tomcat, jetty
- wildfly / jboss, tomee, glasfish, weblogic, webshere
- remote / managed / embedded
- arquillian-Protokoll
- shrinkwrap
- jeweils passendes Java EE API
Maven
- arqullian-bom
- shrinkwrap-*-bom
<dependencyManagement>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>${version.arquillian.core}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap</groupId>
<artifactId>shrinkwrap-bom</artifactId>
...
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap.resolver</groupId>
<artifactId>shrinkwrap-resolver-bom</artifactId>
...
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap.descriptors</groupId>
<artifactId>shrinkwrap-descriptors-bom</artifactId>
...
</dependency>
</dependencyManagement>
ShrinkWrap
- Erstellung von Artefakten
- Programmatisch
- Sehr flexibel -> Gut für Unit-tests
- Beispiel: DeploymentExampleTest
Einzelne Klassen
@Deployment
public static JavaArchive simpleDeployment() {
return ShrinkWrap.create(JavaArchive.class, "simpleDeployment.jar")
.addClass(CurrencyConverterService.class)
.addClass(ExchangeRateRepository.class)
.addClass(ExchangeRate.class)
.addClass(DummyExchangeRateRepository.class)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}
Pakete
@Deployment
public static JavaArchive packages() {
return ShrinkWrap.create(JavaArchive.class, "packages.jar")
.addPackage(CurrencyConverterService.class.getPackage().getName())
.addPackages(true, CurrencyConverterService.class.getPackage().getName())
.addAsManifestResource("META-INF/persistence.xml", "persistence.xml");
}
Java EE Descriptoren
@Deployment
public static WebArchive modifyDescriptor() {
BeansDescriptor beans = Descriptors.create(BeansDescriptor.class)
.createAlternatives().clazz(
DummyExchangeRateRepository.class.getCanonicalName()
).up();
return ShrinkWrap.create(WebArchive.class, "modifyDescriptor.war")
.addClass(JpaExchangeRateRepository.class)
.addClass(DummyExchangeRateRepository.class)
.addAsManifestResource(new StringAsset(beans.exportAsString()), "beans.xml");
}
Webresources
@Deployment
public static WebArchive addWebResources() {
return ShrinkWrap.create(WebArchive.class)
.merge(ShrinkWrap.create(GenericArchive.class).as(ExplodedImporter.class)
.importDirectory("src/main/webapp", Filters.exclude(".*/beans.xml"))
.as(GenericArchive.class),
"/")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsWebInfResource(new StringAsset("<faces-config version=\"2.0\"/>"), "faces-config.xml");
}
Bibliotheken
@Deployment
public static WebArchive addLibraries() {
return ShrinkWrap.create(WebArchive.class, "addLibraries.war")
.addPackage(CurrencyConverterService.class.getPackage().getName())
.addAsLibraries("lib1.jar", "lib2.jar");
}
Dopplung der
Buildsystembeschreibung
- Bibliotheken
- Pre-/Postprocessing
Bibliotheken mit Maven Resolver
@Deployment
public static WebArchive resolveLibraries() {
final File[] files = Maven.configureResolver()
.workOffline()
.loadPomFromFile("pom.xml")
.resolve("de.larmic.butterfaces:components-mojarra").withTransitivity().asFile();
return ShrinkWrap.create(WebArchive.class)
.addAsLibraries(files);
}
Kompletter Maven-Import
@Deployment
public static WebArchive buildFromPom() {
return ShrinkWrap.create(MavenImporter.class)
.loadPomFromFile("pom.xml")
.importBuildOutput()
.as(WebArchive.class);
}
Beispiele
- Direkt im Vortrag verlinkt
-
Java EE 7 Petclinic von Thomas Wöhlke auf github
- Fachlichkeit:
- Tierärzte mit Spezialisierungen
- Haustiere gruppiert in Arten
- Besitzer von Haustieren
- Besitzer kommen mit Haustieren zu einem Termin
- Fork mit Testerweiterungen auf github
Unit-Tests
... für Java EE Komponenten
- Interceptor
- Decorator
- Filter
- JSF-Komponenten
- JPA
- ...
JPA
- TDD / BDD
- Entwicklung von komplexen Mappings
- Schrittweise Erstellung von Queries
- Input- / Output-Tests
- Sehr gute Absicherung für Refactorings
@RunWith(Arquillian.class)
@PersistenceTest
@UsingDataSet("OwnerDaoImplTest.yml")
@Cleanup(strategy = CleanupStrategy.USED_TABLES_ONLY)
public class OwnerDaoImplTest {
@PersistenceContext(unitName = "javaee7petclinic")
private EntityManager entityManager;
@Inject
private OwnerDao ownerDao;
types:
- id: 1
name: Katze
- id: 2
name: Hund
owners:
- id: 1
first_name: Stefan
last_name: Meier
address: Hauptstr.
city: Oldenburg
telephone: 0441-666
- id: 2
first_name: Horst
last_name: Müller
address: Hauptstr.
city: Oldenburg
telephone: 0441-765
pets:
- id: 11
name: Speedy
birth_date: 2015-01-01
type_id: 1
owner_id: 1
- id: 21
name: Bruno
birth_date: 2014-03-04
type_id: 2
owner_id: 2
visits:
- id: 110
visit_date: 2015-01-11
description: Untersuchungen
pet_id: 11
- id: 111
visit_date: 2015-03-11
description: Impfungen
pet_id: 11
- id: 211
visit_date: 2015-03-10
description: Impfungen
pet_id: 21
@Test
public void testFindOwnersWithVisitWithinGivenTimeFrame_DayForLastVisit() {
final List<Owner> owners
= ownerDao.findOwnersWithVisitWithinGivenTimeFrame(
SIMPLE_DATE_FORMAT.parse("2015-03-11"), new Date());
assertThat(owners, CoreMatchers
.<Owner>hasItem(hasProperty("firstName", is(equalTo("Stefan")))));
assertThat(owners, not(CoreMatchers
.<Owner>hasItem(hasProperty("firstName", is(equalTo("Horst"))))));
}
@Test
public void testFindOwnersWithVisitWithinGivenTimeFrame_OnlyOneVisitOnOneDayInPast() {
final List<Owner> owners = ownerDao.findOwnersWithVisitWithinGivenTimeFrame(
SIMPLE_DATE_FORMAT.parse("2015-03-10"),
SIMPLE_DATE_FORMAT.parse("2015-03-10"));
assertThat(owners, not(CoreMatchers
.<Owner>hasItem(hasProperty("firstName",is(equalTo("Stefan"))))));
assertThat(owners, CoreMatchers
.<Owner>hasItem(hasProperty("firstName", is(equalTo("Horst")))));
}
@Test
public void testFindOwnersWithVisitWithinGivenTimeFrame_AllVisitsUntilSomeDateInPast() {
final List<Owner> owners
= ownerDao .findOwnersWithVisitWithinGivenTimeFrame(
SIMPLE_DATE_FORMAT.parse("1990-03-10"),
SIMPLE_DATE_FORMAT.parse("2015-03-10"));
assertThat(owners, CoreMatchers
.<Owner>hasItem(hasProperty("firstName", is(equalTo("Stefan")))));
assertThat(owners, CoreMatchers
.<Owner>hasItem(hasProperty("firstName", is(equalTo("Horst")))));
}
Services
- Zusammenspiel mehrerer Elemente eines Services
- Mit / ohne Datenbank
- Mocks oder Simulatoren
- Angepasstes Deployment
- @Alternative
@RunWith(Arquillian.class)
public class VetWebserviceTest {
@Deployment(testable = false)
public static WebArchive createDeployment() {
WebArchive war;
= ShrinkWrap.create(WebArchive.class, "remote-service-deployment.war")
.addClasses(VetWebservice.class, JaxRsActivator.class, Vets.class)
.addClasses(VetDao.class, Vet.class, Specialty.class)
.addClasses(VetDaoMock.class);
return war
}
@Test
@RunAsClient
public void testFeed() throws IOException {
expect()
.statusCode(200)
.content(RegexMatcher.matchesRegex(
...))
.when()
.get("rest/vets/feed");
}
System
- DB mit Echtdaten (obfuscated) in VM / Container
- Simulatoren für Nachbarsysteme
- Testschnittstellen:
Systemintegration
mit Nachbarsystemen
- Komplett definierter und exklusiver Stand
- Kein Setup bei der Testausführung über reguläre UI / Services
- In der VM/Container vorbereitet
- Sehr robuste und verlässliche Tests
- Relativ schnell
- Idealerweise Ausführung im CI vor Integration
Akzeptanztests
- Externe, fachliche Sicht auf Tests
- Verlinkung zu Anforderungen
- Langfristige Absicherung von Anforderungen
- Klassische Probleme:
- Weit weg vom Code
- Instabil
- Langsam
- Seltene Ausführung
- Kaum Feedback für Entwicklung
- Cucumber-TestRunner mit Arquillian-Support
-
Akzeptanztests von Unit- bis Systemintegrationsebene
- Setup wie bei Systemintegrationstests
- Lokale Ausführung -> schnelles Feedback
- Regelmäßige Ausführung im CI -> schnelles Feedback
-
Eigener Vortrag
Nicht-funktionale Tests
- Mit Gatling, JMeter...
- Über Messpunkte bei der Testausführung
- Indirekt: Laufzeit einer größeren Suite
Zusammenfassung Einsatzbereiche
- Tests immer auf kleinstmöglicher Ebene
- Erst bei konkreten Problemen Tests in höherer Ebene
- Wenige vollumfängliche Integrationstests
- Arquillian Tests kann Zwischenebenen erzeugen
Deployment
- Ein Deployment je Testklasse
- Unit-Test
- Integrationstests
- Idee: Ein Deployment je Test-Classpath
- Auswirkungen auf Projektlayout
- Alternativ: Mehrere Deployments
- Benannte Deployments
- Zuordnung über Namen
- Eigene Events für Hooks
- Nicht im Fokus der arquillian-core-Entwicklung
- Nicht im Fokus anderer Extensions
Dependency Handling
- maven-/gradle-Importer
- Analyse komplexer Projekte dauert
- Alternativ: directory-Importer
- Befüllung mittels maven/gradle
Datenbank Setup (Unit)
- In-Memory-Datenbanken
- Ggf. Testbezogen laden
Datenbank Setup (Integration+)
- Once-Per-Suite
- Dedizierte Daten je Test
- Mögliche Verfahren
- DB Unit
- Spezialwerkzeuge für Datenbanken
- Fertige Container oder VMs
UI-Tests
- Definierter Systemzustand
- Datenbank
- Sonstige Daten/Zustände
- Langsamere Systeme mocken
- Inspections in den Mocks
- Bedienung des UIs vereinfachen
Multi-Threading
- Viel Komplexität
- Ab 4 Threads deutlicher Gewinn
- Interne vs. externe Lösung
Internes Multithreading
- Parallele Ausführung der Tests in eine Suite in einer VM
- Notwendig:
- Synchronisatisiertes Suite-Deployment
- Disjunkte Testdaten
- Threadsave Mocks-/Simulatoren
-
junittoolbox-ParallelSuite
- Nicht mit Arquillian kombinierbar
Externes Multithreading
- Parallele Testausführung mit maven/gradle
- mehrere JVMs
- Notwendig:
- Mehrere Container, DBs und Umsysteme
- Starten, Stoppen, Löschen von Container
- Weitergabe von Portdaten an den Test
- Beispiel: arquillan.xml
- Unterstützung für Seleniumtests
- Bereitstellung von Webdriver-Instanz
- Implementierung des Page-Objekt-Patterns
- Erweiterung des Page-Objekt-Patterns um Komponenten
- Eigener Vortrag
- Untersuchung von Servlet-Requests im Test
- Mocking von Servlet-Responses im Test
- Transaktionssteuerung in Tests
- Ausführung von SQL-Skripten für das Schema
- DBUnit-Integration
- Befüllung einer DB
- Assert auf DB-Ebene
Zusammenfassung
- Ideal für Java EE-Unit-Tests
- Auf allen Teststufen verwendbar
- Viel Flexibilität
- Zugriff auf Testobjekte im Container
- Mocking
- Viel Komplexität
- Suite nicht im Fokus
- Beratung, Coaching und Projektunterstützung
- Java EE
- Buildsysteme gradle und maven/ant-Migration
- Testautomatisierung
- Coach in agilen Projekten
- DevOps