JSF-Anwendungen testen mit
Arquillian Drone und Arquillian WARP
Arquillian ermöglicht "In-Container"-Tests. Will man JSF-Anwendungen
testen, genügt dies nicht, da ja auch Client-seitige Eingaben und Prüfungen
benötigt werden.
Das Arquillian-Projekt Drone verwendet Selenium, um Client-Eingaben
und Prüfungen zu realisieren. Mit den üblichen Arquillian-Möglichkeiten
hat man damit sowohl den Client als auch den Container im Zugriff.
Das Arquillian-Projekt WARP ermöglicht ebenfalls Client- als auch
Server-seitigen Zugriff. Es befindet sich gerade in der Inkubationsphase
als Arquillian-Teilprojekt. Obwohl noch nicht vollständig, erscheint
ein Blick in die aktuellen Möglichkeiten sinnvoll.
6. Arquillian in der Selbstdarstellung
Auszug aus http://www.arquillian.org/
So you can rule your code. Not the bugs.
No more mocks.
No more container lifecycle and deployment hassles.
Just real tests!
Bernd M¨uller, JAX 2014, 15.5.2014 6/61
7. Kurzbeschreibung Arquillian
JBoss’ Test-Framework f¨ur Tests im Container
Dazu JUnit oder TestNG als Test-Runner
Und Test-Enricher, um Tests im Container laufen zu lassen
Und ShrinkWrap als Werkzeug f¨urs Packaging/Deployment
Prinzipieller Ablauf:
Test wird auf Client gepackt
Test und Laufzeiterweiterung wird im Container deployt
Test wird ausgef¨uhrt
Testergebnisse werden an Client zur¨uckgegeben
Test wird undeployt
Aktuell: Arquillian Core Version 1.1.4.Final, 31.3.2014
Bernd M¨uller, JAX 2014, 15.5.2014 7/61
8. Kurzbeschreibung Arquillian
JBoss’ Test-Framework f¨ur Tests im Container
Dazu JUnit oder TestNG als Test-Runner
Und Test-Enricher, um Tests im Container laufen zu lassen
Und ShrinkWrap als Werkzeug f¨urs Packaging/Deployment
Prinzipieller Ablauf:
Test wird auf Client gepackt
Test und Laufzeiterweiterung wird im Container deployt
Test wird ausgef¨uhrt
Testergebnisse werden an Client zur¨uckgegeben
Test wird undeployt
Aktuell: Arquillian Core Version 1.1.4.Final, 31.3.2014
Bernd M¨uller, JAX 2014, 15.5.2014 7/61
9. Kurzbeschreibung Arquillian
JBoss’ Test-Framework f¨ur Tests im Container
Dazu JUnit oder TestNG als Test-Runner
Und Test-Enricher, um Tests im Container laufen zu lassen
Und ShrinkWrap als Werkzeug f¨urs Packaging/Deployment
Prinzipieller Ablauf:
Test wird auf Client gepackt
Test und Laufzeiterweiterung wird im Container deployt
Test wird ausgef¨uhrt
Testergebnisse werden an Client zur¨uckgegeben
Test wird undeployt
Aktuell: Arquillian Core Version 1.1.4.Final, 31.3.2014
Bernd M¨uller, JAX 2014, 15.5.2014 7/61
13. Beispiel: Test-Runner und Deployment
@RunWith(Arquillian.class)
public class CustomerServiceTest {
@Deployment
public static Archive <?> createTestArchive () {
return ShrinkWrap.create(WebArchive.class , "test.war")
.addClasses(Customer.class , CustomerService .class)
.addAsResource("META -INF/persistence.xml")
. addAsWebInfResource (
new File("src/main/webapp/WEB -INF/beans.xml"));
}
...
Bernd M¨uller, JAX 2014, 15.5.2014 9/61
14. Beispiel: Test-Runner und Deployment
@RunWith(Arquillian.class)
public class CustomerServiceTest {
@Deployment
public static Archive <?> createTestArchive () {
return ShrinkWrap.create(WebArchive.class , "test.war")
.addClasses(Customer.class , CustomerService .class)
.addAsResource("META -INF/persistence.xml")
. addAsWebInfResource (
new File("src/main/webapp/WEB -INF/beans.xml"));
}
...
Bernd M¨uller, JAX 2014, 15.5.2014 9/61
15. ShrinkWrap
Werkzeug zur Erstellung/Manipulation von Java-Archiven
(JARs, WARs, EARs)
Fr¨uher eigenst¨andiges JBoss-Projekt, jetzt
Arquillian-Teilprojekt
Fluent-API zum Erstellen eines Archives,
f¨ur sogenannte Micro Deployment,
im Beispiel zwei Klassen und Deployment-Descriptoren
Bernd M¨uller, JAX 2014, 15.5.2014 10/61
21. Beispiel: Zu testende EJB
@Stateless
public class CustomerService {
@PersistenceContext
EntityManager em;
public void persist(Customer customer) {
em.persist(customer );
}
public long getNumberOfCustomers () {
return em. createNamedQuery (
"Customer. getNumberOfCustomers ",
Long.class ). getSingleResult ();
}
}
Bernd M¨uller, JAX 2014, 15.5.2014 12/61
22. Beispiel: Der Test
@RunWith(Arquillian.class)
public class CustomerServiceTest {
@Deployment
public static Archive <?> createTestArchive () { ...}
@Inject
CustomerService customerService ;
@Test
public void testPersist () {
Customer customer = new Customer("Firstname", "Lastname"
assertNull(customer.getId ()); // ueberfluessig
customerService .persist(customer );
assertNotNull(customer.getId ());
}
Bernd M¨uller, JAX 2014, 15.5.2014 13/61
23. Beispiel: Alternativer Test
@Test
public void testPersist2 () {
Customer customer = new Customer("Firstname", "Lastname"
Long before = customerService . getNumberOfCustomers ();
customerService .persist(customer );
Long after= customerService . getNumberOfCustomers ();
assertTrue("Muss ein Customer mehr sein",
before + 1 == after );
}
Bernd M¨uller, JAX 2014, 15.5.2014 14/61
24. Beispiel: 3. Alternative mit Persistenzkontext
@RunWith(Arquillian.class)
public class CustomerServiceTest {
@PersistenceContext
EntityManager em;
@Test
public void testPersist3 () {
Customer customer = new Customer("Firstname", "Lastname"
Long before = em. createNamedQuery (
"Customer. getNumberOfCustomers ", Long.class)
. getSingleResult ();
customerService .persist(customer );
Long after= em. createNamedQuery (
"Customer. getNumberOfCustomers ", Long.class)
. getSingleResult ();
assertTrue("Muss ein Customer mehr sein",
before + 1 == after );
}
25. Beispiel: 3. Alternative mit Persistenzkontext
@RunWith(Arquillian.class)
public class CustomerServiceTest {
@PersistenceContext
EntityManager em;
@Test
public void testPersist3 () {
Customer customer = new Customer("Firstname", "Lastname"
Long before = em. createNamedQuery (
"Customer. getNumberOfCustomers ", Long.class)
. getSingleResult ();
customerService .persist(customer );
Long after= em. createNamedQuery (
"Customer. getNumberOfCustomers ", Long.class)
. getSingleResult ();
assertTrue("Muss ein Customer mehr sein",
before + 1 == after );
}
Existiert, da Test in Container
29. Arquillian Drone
Arquillian-Erweiterung f¨ur funktionale Tests
Basiert auf Selenium
Selenium-Slogan: Selenium automates browsers
WebDriver: M¨oglichkeit zur Browser-Steuerung, u.a. mit Java
API
WebDriver API das einzig neu zu lernende
¨Anderung des prinzipiellen Ablaufs: Tests auf Client
Kombination Client und Server m¨oglich
Version aktuell: 1.3.0.Final
Version 2.0.0.Alpha1 mit ¨uberarbeitetem/entschlacktem API
in Entwicklung (von uns nicht verwendet)
Bernd M¨uller, JAX 2014, 15.5.2014 19/61
30. Die n¨otigen Erweiterungen/¨Anderungen
@RunWith(Arquillian.class)
public class DroneUsedTest {
@ArquillianResource
URL deploymentURL;
@Drone
WebDriver driver;
@Deployment(testable = false)
public static Archive <?> createTestArchive () { ...}
@ArquillianResource: das URL des Deployments
@Drone: das WebDriver oder DefaultSelenium API
Keine Tests im Server
Bernd M¨uller, JAX 2014, 15.5.2014 20/61
31. Die n¨otigen Erweiterungen/¨Anderungen
@RunWith(Arquillian.class)
public class DroneUsedTest {
@ArquillianResource
URL deploymentURL;
@Drone
WebDriver driver;
@Deployment(testable = false)
public static Archive <?> createTestArchive () { ...}
@ArquillianResource: das URL des Deployments
@Drone: das WebDriver oder DefaultSelenium API
Keine Tests im Server
Bernd M¨uller, JAX 2014, 15.5.2014 20/61
32. Die n¨otigen Erweiterungen/¨Anderungen
@RunWith(Arquillian.class)
public class DroneUsedTest {
@ArquillianResource
URL deploymentURL;
@Drone
WebDriver driver;
@Deployment(testable = false)
public static Archive <?> createTestArchive () { ...}
@ArquillianResource: das URL des Deployments
@Drone: das WebDriver oder DefaultSelenium API
Keine Tests im Server
Bernd M¨uller, JAX 2014, 15.5.2014 20/61
33. Die n¨otigen Erweiterungen/¨Anderungen
@RunWith(Arquillian.class)
public class DroneUsedTest {
@ArquillianResource
URL deploymentURL;
@Drone
WebDriver driver;
@Deployment(testable = false)
public static Archive <?> createTestArchive () { ...}
@ArquillianResource: das URL des Deployments
@Drone: das WebDriver oder DefaultSelenium API
Keine Tests im Server
Bernd M¨uller, JAX 2014, 15.5.2014 20/61
34. Falls nicht nur Client-Tests
@RunWith(Arquillian.class)
public class DroneUsedTest {
@Drone
WebDriver driver;
@Deployment
public static Archive <?> createTestArchive () { ... }
@Test
@RunAsClient
@InSequence (1)
public void createCustomer( @ArquillianResource URL url) {
@Test // im Server
@InSequence (2)
public void countCustomers () { ... }
Auch Tests im Server m¨oglich, daher Unterscheidung
Reihenfolge der Tests (Widerspruch zur reinen Lehre?)
35. Falls nicht nur Client-Tests
@RunWith(Arquillian.class)
public class DroneUsedTest {
@Drone
WebDriver driver;
@Deployment
public static Archive <?> createTestArchive () { ... }
@Test
@RunAsClient
@InSequence (1)
public void createCustomer( @ArquillianResource URL url) {
@Test // im Server
@InSequence (2)
public void countCustomers () { ... }
Auch Tests im Server m¨oglich, daher Unterscheidung
Reihenfolge der Tests (Widerspruch zur reinen Lehre?)
36. Falls nicht nur Client-Tests
@RunWith(Arquillian.class)
public class DroneUsedTest {
@Drone
WebDriver driver;
@Deployment
public static Archive <?> createTestArchive () { ... }
@Test
@RunAsClient
@InSequence (1)
public void createCustomer( @ArquillianResource URL url) {
@Test // im Server
@InSequence (2)
public void countCustomers () { ... }
Auch Tests im Server m¨oglich, daher Unterscheidung
Reihenfolge der Tests (Widerspruch zur reinen Lehre?)
37. Versteht Arquillian Maven’s POM ?
Nein, aber ShrinkWrap
Es gibt einen ShrinkWrap-Resolver, der Maven und Gradle
unterst¨utzt
Jetzt Beispiel:
JSF-Seite zur Kundenneuanlage
Navigation zu Kundenanlage-Erfolgreich-Seite, falls kein Fehler
Test, ob Navigation funktioniert
Test, ob Daten im Server
Bernd M¨uller, JAX 2014, 15.5.2014 22/61
38. Versteht Arquillian Maven’s POM ?
Nein, aber ShrinkWrap
Es gibt einen ShrinkWrap-Resolver, der Maven und Gradle
unterst¨utzt
Jetzt Beispiel:
JSF-Seite zur Kundenneuanlage
Navigation zu Kundenanlage-Erfolgreich-Seite, falls kein Fehler
Test, ob Navigation funktioniert
Test, ob Daten im Server
Bernd M¨uller, JAX 2014, 15.5.2014 22/61
39. Das (fast) komplette Beispiel (Teil 1)
@RunWith(Arquillian.class)
public class CreateCustomerTest {
private static final String
WEBAPP_SRC = "src/main/webapp";
@Drone
WebDriver driver;
@Inject
CustomerRepository customerRepository ;
Bernd M¨uller, JAX 2014, 15.5.2014 23/61
42. Das (fast) komplette Beispiel (Teil 4)
@Test // im Server
@InSequence (2)
public void countCustomers () {
assertEquals (1, customerRepository .getCustomers (). size ());
}
Bernd M¨uller, JAX 2014, 15.5.2014 26/61
43. @Drone im Detail
Injection in Klasse
Class-Based Life-Cycle
Analog zu @BeforeClass / @AfterClass
Injection in Methode
Method-based Life-Cycle
Analog zu @Before / @After
Mehrere Injektionen ¨uber Qualifier m¨oglich
Verschiedene Driver: WebDriver, FirefoxDriver, ChromeDriver,
InternetExplorerDriver, SafariDriver
Durch Selenium Unterst¨utzung von XPath-Ausdr¨ucken in
Test-Asserts
Bernd M¨uller, JAX 2014, 15.5.2014 27/61
44. @Drone im Detail
Injection in Klasse
Class-Based Life-Cycle
Analog zu @BeforeClass / @AfterClass
Injection in Methode
Method-based Life-Cycle
Analog zu @Before / @After
Mehrere Injektionen ¨uber Qualifier m¨oglich
Verschiedene Driver: WebDriver, FirefoxDriver, ChromeDriver,
InternetExplorerDriver, SafariDriver
Durch Selenium Unterst¨utzung von XPath-Ausdr¨ucken in
Test-Asserts
Bernd M¨uller, JAX 2014, 15.5.2014 27/61
45. @Drone im Detail
Injection in Klasse
Class-Based Life-Cycle
Analog zu @BeforeClass / @AfterClass
Injection in Methode
Method-based Life-Cycle
Analog zu @Before / @After
Mehrere Injektionen ¨uber Qualifier m¨oglich
Verschiedene Driver: WebDriver, FirefoxDriver, ChromeDriver,
InternetExplorerDriver, SafariDriver
Durch Selenium Unterst¨utzung von XPath-Ausdr¨ucken in
Test-Asserts
Bernd M¨uller, JAX 2014, 15.5.2014 27/61
46. @Drone im Detail
Injection in Klasse
Class-Based Life-Cycle
Analog zu @BeforeClass / @AfterClass
Injection in Methode
Method-based Life-Cycle
Analog zu @Before / @After
Mehrere Injektionen ¨uber Qualifier m¨oglich
Verschiedene Driver: WebDriver, FirefoxDriver, ChromeDriver,
InternetExplorerDriver, SafariDriver
Durch Selenium Unterst¨utzung von XPath-Ausdr¨ucken in
Test-Asserts
Bernd M¨uller, JAX 2014, 15.5.2014 27/61
47. @Drone im Detail
Injection in Klasse
Class-Based Life-Cycle
Analog zu @BeforeClass / @AfterClass
Injection in Methode
Method-based Life-Cycle
Analog zu @Before / @After
Mehrere Injektionen ¨uber Qualifier m¨oglich
Verschiedene Driver: WebDriver, FirefoxDriver, ChromeDriver,
InternetExplorerDriver, SafariDriver
Durch Selenium Unterst¨utzung von XPath-Ausdr¨ucken in
Test-Asserts
Bernd M¨uller, JAX 2014, 15.5.2014 27/61
49. Arquillian Warp
Client-seitige Tests mit Zusicherungen/Pr¨ufung von
Server-seitiger Logik und Zustand
Keine weiteren Bibliotheken, wie etwa Selenium, ben¨otigt
Kein UI ben¨otigt (Headless)
Speziell f¨ur Servlets und Servlet-basierte Systeme gemacht
Im Augenblick nur Servlets direkt und JSF 2 unterst¨utzt
Arquillian Spring Framework Extension enth¨alt Warp Spring
MVC Extension (nicht betrachtet)
Offizieller Nachfolger von JSFUnit
Aktuell: Version 1.0.0.Alpha7, 11.3.2014
Bernd M¨uller, JAX 2014, 15.5.2014 29/61
50. Die prinzipielle Idee hinter WARP
Initiierung eines HTTP-Requests auf Client-Seite z.B. mit
WebDriver
Im selben Request-Zyklus Ausf¨uhren von Server-seitigen Tests
im Container
Dazu:
Initiieren des Request ¨uber Interface Activity
Inspizieren des Server-Zustands ¨uber Klasse Inspection
Aktivity mit perform()-Methode
Inspection mit JUnit/TestNG-Tests zum
”
richtigen“ Zeitpunkt
optional: Gruppieren und Observieren von Requests
Bernd M¨uller, JAX 2014, 15.5.2014 30/61
51. Die n¨otigen Erweiterungen/¨Anderungen
@WarpTest f¨ur Test-Klasse
@RunAsClient f¨ur Test-Klasse
@BeforeServlet, @AfterServlet f¨ur Servlet-Tests
@BeforePhase, @AfterPhase (6 Phasen) f¨ur JSF-Tests
Im Folgenden 3 Beispiele
Existieren die richtigen Beans?
Validierung
Navigation
Bernd M¨uller, JAX 2014, 15.5.2014 31/61
52. Die n¨otigen Erweiterungen/¨Anderungen
@WarpTest f¨ur Test-Klasse
@RunAsClient f¨ur Test-Klasse
@BeforeServlet, @AfterServlet f¨ur Servlet-Tests
@BeforePhase, @AfterPhase (6 Phasen) f¨ur JSF-Tests
Im Folgenden 3 Beispiele
Existieren die richtigen Beans?
Validierung
Navigation
Bernd M¨uller, JAX 2014, 15.5.2014 31/61
53. Die n¨otigen Erweiterungen/¨Anderungen
@WarpTest f¨ur Test-Klasse
@RunAsClient f¨ur Test-Klasse
@BeforeServlet, @AfterServlet f¨ur Servlet-Tests
@BeforePhase, @AfterPhase (6 Phasen) f¨ur JSF-Tests
Im Folgenden 3 Beispiele
Existieren die richtigen Beans?
Validierung
Navigation
Bernd M¨uller, JAX 2014, 15.5.2014 31/61
54. Die n¨otigen Erweiterungen/¨Anderungen
@WarpTest f¨ur Test-Klasse
@RunAsClient f¨ur Test-Klasse
@BeforeServlet, @AfterServlet f¨ur Servlet-Tests
@BeforePhase, @AfterPhase (6 Phasen) f¨ur JSF-Tests
Im Folgenden 3 Beispiele
Existieren die richtigen Beans?
Validierung
Navigation
Bernd M¨uller, JAX 2014, 15.5.2014 31/61
55. 1. Beispiel: Existieren die richtigen Beans?
Weld-Manual: Einloggen ¨uber JSF-View-Bean
Pr¨ufen auf Benutzername/Passwort ¨uber EJB
CDI-produzierte Customer-Instanz im Session-Scope
Die Existenz dieser Instanz soll ¨uberpr¨uft werden
Bernd M¨uller, JAX 2014, 15.5.2014 32/61
60. Struktur zur Verdeutlichung
Warp
.initiate(new Activity () {
...
})
.inspect(new Inspection () {
...
})
Aktivit¨at auf dem Client initiieren
Inspektion auf dem Server ausf¨uhren
Bernd M¨uller, JAX 2014, 15.5.2014 37/61
65. 2. Beispiel: Validierung
Credentials werden falsch eingegeben (zu kurz)
JSF erzeugt in Phase 3 FacesMessage-Instanzen
Pr¨ufen vor und nach Phase 3, ob Messages existieren oder
nicht
Bernd M¨uller, JAX 2014, 15.5.2014 40/61
66. Klasse Credentials
@Named
@RequestScoped
public class Credentials {
@NotNull
@Size(min = 6, max = 30)
private String email;
@NotNull
@Size(min = 6, max = 20)
private String password;
...
Deutsche Fehlermeldung f¨ur @Size: ”muss zwischen 6 ...
Bernd M¨uller, JAX 2014, 15.5.2014 41/61
72. 3. Beispiel: Navigation mit Redirect
Einfache JSF-Seiten mit Navigation und Redirect
Noch nicht erw¨ahntes WARP-Feature: Gruppieren von
Inspektionen auf Server basierend auf Request-Folge
Pr¨ufen der View-Id nach Restore-View-Phase
Bernd M¨uller, JAX 2014, 15.5.2014 46/61
73. JSF-Seiten mit Navigation
redirect-source.xhtml
<h:form id="form">
Seite verwendet #{ redirectSourceBean .name} <br />
<h:commandButton id="redirect"
action=" redirect-target ? faces-redirect=true"
value="Redirect to ’redirect-target .jsf ’" />
</h:form >
redirect-target.xhtml
<h:form id="form">
Seite verwendet #{ redirectTargetBean .name} />
</h:form >
Bernd M¨uller, JAX 2014, 15.5.2014 47/61
79. Auswahl des richtigen Request
Da mehrere Request getriggert werden k¨onnen, muss man den
richtigen (den, den man observieren will) ausw¨ahlen
Dazu das HttpRequestFilter-Interface
Damit spezifiziert man den zu verwendenden HttpRequest der
aktuellen Gruppe
Bernd M¨uller, JAX 2014, 15.5.2014 53/61
80. Aus
”
Dokumentation“
import static org.jboss.arquillian.warp
.client.filter.http.HttpFilters.request;
// will accept only requests for HTML
... group ()
.observe(request (). uri (). contains(".html"))
// will accept only REST requests for JSON
... group ()
.observe(request (). header ()
.containsValue("Accept", "application/json"))
// will accept only POST requests
... group ()
.observe(request (). method (). equal(POST ))
Bernd M¨uller, JAX 2014, 15.5.2014 54/61
81. Was kann alles injiziert werden?
Injizierbar ¨uber @ArquillianResource
Servlet-Ressourcen
ServletRequest or HttpServletRequest
ServletResponse or HttpServletResponse
Bernd M¨uller, JAX 2014, 15.5.2014 55/61
83. Wo Licht ist, ist auch Schatten
Sehr schlechte Doku
Noch viele/schwere Fehler
Scheinbar Ein-Mann-Projekt: Luk´aˇs Fryˇc (Project Lead)
Bernd M¨uller, JAX 2014, 15.5.2014 57/61