Landung der Aliens

Arquillian in der Praxis

Stefan Hildebrandt / @hildebrandttk

Mein Einstieg

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"
      • Datenbanksetup
      • Testdaten

Java EE 6

Java EE ohne Spring

... ohne Springtest

Was nun?

Arquillian!

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

Hinter den Kulissen

Client-/Server-Mode

Client

Server

Containermanagement

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

Projekteinbindung

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>

Artefakt-Erstellung

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);
}

Einsatzbereiche

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

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:
    • Service-Ebene
      • Local
      • Remote
    • UI

Beispiel UI-Test

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

cukespace

  • Cucumber-TestRunner mit Arquillian-Support
  • Akzeptanztests von Unit- bis Systemintegrationsebene
  • Setup wie bei Systemintegrationstests
    • Wesentlich robuster
  • 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

Testpyramide

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

Ausführungszeit

Deployment

  • Ein Deployment je Testklasse
    • Unit-Test
    • Integrationstests

Arquillian Suite Extension

  • 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
    • Kompatibilitätsprobleme

Tomee

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+)

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

Erweiterungen

arquillian-cube

  • Starten, Stoppen, Löschen von Container
  • Weitergabe von Portdaten an den Test
  • Beispiel: arquillan.xml

Drone/Graphene

  • Unterstützung für Seleniumtests
  • Bereitstellung von Webdriver-Instanz
  • Implementierung des Page-Objekt-Patterns
  • Erweiterung des Page-Objekt-Patterns um Komponenten
  • Eigener Vortrag

Warp

  • Untersuchung von Servlet-Requests im Test
  • Mocking von Servlet-Responses im Test

Transaction

  • Transaktionssteuerung in Tests

Persistence

  • Ausführung von SQL-Skripten für das Schema
  • DBUnit-Integration
    • Befüllung einer DB
    • Assert auf DB-Ebene

Dokumentation

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

Alternativen

Folien

h9t.eu/s/aip
h9t.eu/s/aip

Stefan Hildebrandt - consulting.hildebrandt.tk

  • Beratung, Coaching und Projektunterstützung
  • Java EE
  • Buildsysteme gradle und maven/ant-Migration
  • Testautomatisierung
  • Coach in agilen Projekten
  • DevOps
Datenschutz Impressum