Hilfe zur Selbsthilfe

Testautomatisierung im Browser
aus Entwicklersicht

Stefan Hildebrandt / @hildebrandttk

Anmerkungen

Weitere Informationen in der Referentenansicht.
Popup mit [s] öffnen

Komplettes Beispielprojekt auf github

Warum Selbsthilfe?

Ansehen der IT bei der Geschäftsführung, Anwendern, ... ?!?

Termintreue?

Schätzungen?

#noestimates

Fehlermeldungen vor Produktivgang

Fehlermeldungen nach Produktivgang

Warum hat ein Unternehmen
Softwareentwickler?

Um Software zu entwickeln?

Softwareentwicklung ist kein Selbstzweck!

Software soll Geschäftswert liefern

Softwareentwickler sollen Geschäftswert liefern

Agile

  1. Erzeugung von Geschäftswert
  2. Feedback
  3. Nachhaltige Entwicklung

Geschäftswerte:

Produkte

Prozesse

Services

Vertriebswege

Wissen über Kundenwünsche

Wettbewerbsvorteile

Reaktionsmöglichkeiten

IT als Enabler

Überlebenswichtig

  • Quelle
  • Neckermann
  • ...

Messbare Erzeugung von Geschäftswert

Geschäftsidee Anforderung Umsetzung Businessanalyse Entwicklung Test ???

Entwickler brauchen:

Feedback ...

bei der Weiterentwicklung

für Updates

für Refactorings

... für eine sorgenfreiere Entwicklung

Feedback

... vom der QA:

schnell

Janusz Leidgens (@Killerdackel) 26.06.13 15:16:
Der @dtanzer sagt seperate Testabteilungen brauchen zu lange.
Unsere Tester testen über Nacht, klappt bisher gut. #dwx13

sehr schnell

Ziel: 95% Aussage nach max: 30 Minuten

Noch schneller

VOR jedem Commit auf den Masterbranch

Funktionsumfang wächst kontinuierlich ...

Feedback wird ...

quantitativ schlechter

qualitativ schlechter

Entwicklung

vs.

QA

QA findet Fehler kurz vor dem Releasedatum

QA findet Fehler vor dem Releasedatum NICHT

Release hat keine Fehler

QA gewinnt immer!

Entwicklung

mit

QA

Automatische Reggressionstests

schnell

skallierbar

verlässlich

branchbar

vorwurfsfrei

günstig

Mehr Zeit für fachliches Feedback

Feedback

... vom Anforderer:

Wir gehen ans äußerste

und reden miteinander

Kurze Interationen

Possible shipping Software

Feedback

... vom Anwender:

Geschäftsidee Anforderung Umsetzung Businessanalyse Entwicklung Test Feedback vom Anwender

Kleine Schritte

  • MVPs
  • Dimensional Planning

Nachhaltige Entwicklung

Verantwortung übernehmen

Pull

Testpyramiede(n)

Pyramiede, nicht Eistüte!

Im Java EE Stack

Unittests für Logik und Berechnungen

  • Junit
  • TestNG
  • (Mocking)

Behaviour Driven Development

  • spock
  • scalatest

Unittests für Javascript im Broswer

  • qunit
  • Mocking

Behaviour Driven Development with JavaScript

Jasmine

Integrationstests

  • Modulintern mit Datenbank
  • Modulinteraktionen mit Mocks
  • Komplett ohne Client

Integrationstests

  • spring-test
  • Arquillian

Test von JSF / Wicket / GWT / Vaadin?

Erhöht das die Testabdeckung?

Wo, ...?

Wie, ...?

Entwickeranforderungen an Tests...

passende Tests für jeden Versionsstand

für einen Brach passend / anpassbar

von jedem Entwickler ausführbar

vom CI-Server ausführbar

Vertretbarer Änderungsaufwand

Verlässlich

Anforderungen der QA-Abteilung

Ohne Programmieren

Was Vertriebler von X angeboten hat

Was vom Martführer

QTP

„QTP

... der Mercedes unter den Testtools”

- Mercedes kann nichts für den Vergleich -

Der Preis ...

Die Spritzigkeit ...

Die Wendigkeit ...

Der Komfort ...

... aber man kommt mit allen ans Ziel

Stärken

  • Integration mit
    • Quality Center
    • Service Visualization
  • Tester ohne Automatisierungskentnisse können eingebunden werden

Nicht die Werkzeuge, ...

... sondern die Prozesse

  • ... sind meistens nicht schnell genug
  • ... dienen der Abgrenzung der QA-Abteilung
  • ... sind schlecht für das Vertrauen in die Entwicklung

thoughtworks - Technology Radar

78. Heavyweight test tools

thoughtworks - Technology Radar

on Hold - da nicht so richtig Agil

  • Aufwändig in Teams zu intgrieren
    • Steile Lernkurve
    • Teure / begrenzte Lizenzen
  • Modelgetriebener Ansatz führt zu “false positives”

Wie nun?

Testautomatisierung ist
Softwareentwicklung

QA'ler sind häufig
keine Softwareentwickler

Abgestimmte Anwendung der
Testmöglichkeiten

  • Unittest
  • ...
  • Integrationtest

Anwendung muss testbar
entwicklet werden

Nur noch Entwickler?

Testautomatisierung ist
Teamaufgabe

Review der Automomatisierung

Test der richtigen fachlichen Umsetzung

Überblick über die fachliche Testabdeckung

QA == Quality Assistance?

Die QA will nicht?

Festnageln!

Branchentwicklung mit kompletter CI Infrastruktur

z.B. VMs mit VirtualBox, Vagrant und Puppet

  • lokal
  • CI-Umgebung
  • Test Stages
  • Produktion

Selenium

  • Abgelöst durch Selenium WebDriver

Selenium WebDriver

  • Gute Sprachunterstützung
  • Gute Browserunterstützung
  • Auch iOS und Android

WebDriver GettingStarted

WebDriver driver = new HtmlUnitDriver();

// And now use this to visit Google
driver.get("http://www.google.com");

// Find the text input element by its name
WebElement element = driver.findElement(By.name("q"));

// Enter something to search for
element.sendKeys("Cheese!");

// Now submit the form. WebDriver will find the form for us from the element
element.submit();
    

... mit Ajax

long end = System.currentTimeMillis() + 5000;
while (System.currentTimeMillis() < end) {
   WebElement resultsDiv = driver.findElement(By.className("gssb_e"));

   // If results have been returned, the results are displayed in a drop down.
   if (resultsDiv.isDisplayed()) {
     break;
   }
}

// And now list the suggestions
List<WebElement> allSuggestions
   = driver.findElements(By.xpath("//td[@class='gssb_a gbqfsf']"));

for (WebElement suggestion : allSuggestions) {
   System.out.println(suggestion.getText());
}
    

PageObject

public class LoginPage {
  public LoginPage(WebDriver driver) {
    this.driver = driver;
    ...
  }

  By usernameLocator = By.id("username");
  By passwordLocator = By.id("passwd");
  By loginButtonLocator = By.id("login");

  public LoginPage typeUsername(String username) {
    driver.findElement(usernameLocator).sendKeys(username);
    return this;
  }
  ...
}
    

geb - gebish.org

  • Basis ist Selenium WebDriver
  • Bildet eine Abstraktionsebene
  • Bietet Strukturierungshilfen
  • Ajax - waitFor
  • Ist in Groovy Implementiert

Groovy?

missingProperty und
missingMethod

... Ideal für DSLs

JVM

... nahtloser Übergang zur Applikation

Groovy!

Groovy - Klasse

package tk.hildebrandt.hc2013

class Example {
}
            

Groovy - Variablen & Methoden

class Example {
   def foo;
   String bar;

   def doFoo(def foo){
      def bar
   }

   boolean doBar(){
      bar
   }
}
            

Groovy - Collections

@Test
void callWithList(){
   withList([1, 1, 2, 3, 5])
}

void withList(def list) {
   list.each {
      entry ->
         println "${entry}"
   }
   list.eachWithIndex {
      entry, index ->
         println "${index}: ${entry}"
   }
}
            

Groovy - Maps

void callWithMap() {
   withMap(test: '123', name: '324')
}

void withMap(def map) {
   map.each {
      key, value ->
         println "${key}: ${value}"
   }
}
            

Groovy - Closures

void callWithClosure() {
   assert withClosure {
      final boolean result = 1 == 1
      println "within closure ${result}"
      result
   }
}

def withClosure(Closure<Boolean> closure) {
               closure.call()
}
            

Power Assertions

final String test = "test"
assert "test" == test && "test2" == test
               

Ausgabe

assert "test" == test && "test2" == test
              |  |    |          |  |
              |  test false      |  test
              true               false
            

Geb: Browser

  • Referenz zum WebDriver
  • Aktuelle Seite
  • Reporting
Browser.drive {
   go "http://google.com/ncr"
}
            

Geb: jQuery-Style- Selector

$("input", name: "q")
$("li.g", 0).find("a.l")
$("p", text: "Hans")
            

Geb: Navigator

  • Wrapped Selenium WebElement
  • Ausgangspunkt für weitere Suche
  • Werte beziehen: value() / text()
  • Werte setzen: value(...)
  • Aktionen ausführen: click(), ...
$("input", name: "q").value("wikipedia")
$("select").value("wikipedia", "google")
assert firstLink.text() == "Wikipedia"
firstLink.click()
            

Geb - Skripting

@Test
public void testSearch(){
   Browser.drive {
      go "http://google.com/ncr"
      assert title == "Google"
      $("input", name: "q").value("wikipedia")
      waitFor { title.endsWith("Google Search") }
      def firstLink = $("li.g", 0).find("a.l")
      assert firstLink.text() == "Wikipedia"
      firstLink.click()
      waitFor { title.startsWith("Wikipedia") }
   }
}
            

Geb: Page

class GoogleStartPage extends geb.Page {
   static url = "http://google.com/ncr"

   static at = { title == "Google" }

   static content = {
      searchField { $("input", name: "q") }

      searchButton(to: GoogleResultsPage) {
         $("input", type: "submit")
      }
   }
}
            

Geb: Module

class SearchModule extends geb.Module {

   static content = {
      searchField { $("input", name: "q") }

      searchButton(to: GoogleResultsPage) {
         $("input", )
      }
   }

}
            

Geb: Module

class GoogleStartPage extends geb.Page {
   static content = {
      search { module SearchModule }
   }
}
            

Geb: Module

public void testSearchWithPageAndModule(){
   to GoogleStartPage
   assert at(GoogleStartPage)
   search.searchField.value("wikipedia")
   waitFor { at GoogleResultsPage }
   assert searchFirstResultLink.text() == "Wikipedia";
}
            

Geb: Module mit Basis

static content = {
   results { moduleList ResultModule, $("li.g") }
   result { i -> results[i] }
   firstResult { result(0) }
}
            

Geb: Module mit Parameter

static content = {
  myContent { index ->
    moduleList MyModule,
    $(".myModuleClass"),
    index,
    myParam: 'param value' }
}
            

Geb: Module für Komponentenbibliotheken

  • jQuery UI
  • Primefaces
  • Icefaces
  • Richfaces
  • ...

Web 2.0+: Async!

Geb: Async im "static content"

static content = {
  dynamicallyAdded(wait: true) { $("p.dynamic") }
  dynamicallyAddedOrNot(wait: true, required: false)
   { $("p.dynamicButOptional") }
  dynamicallyAddedOrNot(wait: "ajax", required: false)
   { $("p.dynamicButOptional") }
}
//Exception nach dem Wait-Timeout
assert dynamicallyAdded.text() == "I'm here now"
assert dynamicallyAddedOrNot.text() == "I'm here now"
//nach dem Wait-Timeout ist das Value null
   || dynamicallyAddedOrNot.value() == null
            

Geb: Async mit waitFor

waitFor { title.endsWith("Google Search") }
waitFor { at GoogleResultsPage }
waitFor 30 { at GoogleResultsPage }
waitFor ("searchResult", { at GoogleResultsPage })
            

Alle Logik in Pages und Module

Candies

Download

  • Mit WebDriver nicht direkt möglich
    • Nur per Hack über Download-Dialog und sendKeys
  • geb transferiert Session und Location in einen HttpURLConnection
  • Download ohne Browser

interact

  • Möglichkeit komplexe Operationen mit dem WebDriver durchzuführen
  • Alternative Syntax zu Selenium Actions

interact

interact {
    keyDown(Keys.SHIFT)
    doubleClick($('li.clicky'))
    keyUp(Keys.SHIFT)
}
            

interact

interact {
    clickAndHold($('#element'))
    moveByOffset(400, -150)
    release()
}
            
interact {
    dragAndDropBy($('#element'), 400, -150)
}
            

Javascript

  • browser.js.someGlobalFunction('anArg')
  • $('selector').jquery.click()

Frames und Windows

  • withFrame
  • withWindow
  • withNewWindow

Frames und Windows

               
withWindow('MyNewTab') {
   $('#MyNewTabCloseButton').click()
}               
            

Geb: GebConfig.groovy

  • Groovy ConfigSlurper Datei
  • Named Closures
  • Kein Rahmen

Auswahl Browser

driver = "ie"
driver = "chome"
driver = {
   def ffDriver = new FirefoxDriver()
   ffDriver.manage().window().maximize()
   return ffDriver
}
            

Firebug

driver = {
   def FirefoxProfile profile = new FirefoxProfile();
   URL dir_url = ClassLoader.getResource("/firebug-1.12.0-fx.xpi");
   if(dir_url){
      File file = new File(dir_url.toURI());
      profile.addExtension(file);
      profile.setPreference(
        "extensions.firebug.currentVersion",
        "1.12.0");
   }
   def ffDriver = new FirefoxDriver(profile)
}
            

Timing-Presets

waiting {
   timeout = 10
   retryInterval = 0.5
   presets {
      ajax {
         timeout = 3
         retryInterval = 0.5
      }
   }
}
            

Environments

environments {
   'phantomjs' {
      driver = {
         final capabilities = new DesiredCapabilities()
         capabilities.setCapability("javascriptEnabled", true)
         final phantomJSDriver = new PhantomJSDriver(capabilities)
         phantomJSDriver
            .manage()
            .window()
            .setSize(new Dimension(1028, 768))
         return phantomJSDriver
      }
   }
}
            
mvn -Dgeb.env=phantomjs test
            

Browserunterstützung WebDriver

  • Firefox ab 3.6
  • IE 7,8,9
  • Opera 8,9
  • HtmlUnit

Browserunterstützung WebDriver - 3rd-Party

  • Google Chrome und Chromium
  • PhantomJS
  • Android Chrome (EMU und Remote)
  • iOS Safari (EMU und Remote)
  • Opera 10, 11

Browserunterstützung geb

  • htmlunit
  • firefox
  • ie
  • chrome

Kombination mit Arquillian

  • Test mit @TestRunner(Arquillian.class) annotieren
  • @Deployment nur einmal ausführen
  • Bei Pageentwicklung gegen laufende Instantz

Spock

Behavior Driven Development

Spock

  • Namenswahl frei
  • Struktur
    1. Vorbedingung (given)
    2. Aktion (when)
    3. Prüfungen (then)
  • "then"-Bock enthält implizite assertions

Spock

class ExamplePageAndModuleSpec extends geb.spock.GebSpec {

   def "test search with page and module"() {
      when: {
         to GoogleStartPage
         search.searchField.value("wikipedia")
         waitFor { at GoogleResultsPage }
      }
      then: {
         searchFirstResultLink.text() == "Wikipedia"
      }
   }
}
            

Reporting

  • Automatisch bei jedem Seitewechsel und bei Fehlern
  • Aktivierung durch Ableiten von
    • geb.spock.GebReportingSpec
    • geb.junit4.GebReportingTest
    • ...
  • browser.report('<name>')

Cucumber

  1. Behavior Driven Development
  2. Umgangsprachliche Beschreibung
  3. Mehrere reale Sprachen unterstützt
  4. Ideal für Anforderer und Fachtester

Cucumber

Feature: Password management
  Scenario: Forgot password
    Given a user with email "cukes@cukes.info" exists
    When I ask for a password reset
    Then an email with a password reset link should be sent
            

Lasttests

Mit JMeter® und geb

  • Wiederverwendung von Seiten / Tests
    • Relativ leicht zu erstellen
    • Werden automatisch mitgepflegt
    • Korrekte Bedienung
    • Regressionssicher

Lasttests

Mit JMeter® und geb

  • Begrenzte Last
    • Resourcenbedarf für die Browser
    • Erhöhung durch Remoteserver möglich

Sonar

  • z.B. in Verbindung mit Arquilliantests
  • Als Unit- und Integrationstestabdeckung

Prozess für Tests mit Browserinteraktion

Unittests

  • Für selbst erstellte UI-Fuktionalität
  • Detailprüfungen in isolierter Umgebung
  • Verwendung von Mocks / kleinen Deployments
  • Schnelle Ausführung
  • Sehr robust

Komponententests

  • Für Seiten / Abläufen als Ganzes
  • Mit fixer DB und Umsystemen oder Mocks
  • Relativ schnell, wenn parallisiert
  • Robust

Systemintegrationstests

  • Grundsätzlich weniger robust
    • Ausschluss von Bedienungsproblemen durch Verwendung der selben Pages
  • Relativ langsamm
  • Parallisierung fördern

Fachliche Akzeptanztests

  • Auf der tiefst möglichen Ebene implementieren
  • Wenn möglich ohne UI

Vorteile

  • Fehler durch Änderung der Seitenstruktur fallen sehr früh auf
  • Sehr gute Abdeckung des deklarativen UI-Codes
  • Anforderungen an in UI implementierte Features über Tests gut dokumentiert

Best Practices

  • Wenn ohne UI testbar --> Ohne UI

Best Practices

  • Wiederverwendung von Pages und Modulen

Best Practices

  • Im Test/Speck kein wait
    • Asychronität bleibt in der Page / Modul
    • Idealerweise durch Marker in Page auswerten

Best Practices

  • Anwendung testbar gestallten, z.B. css Klassen als Alternative zu IDs, falls diese durch Frameworks nicht stabil sind.

Best Practices

Anwendung testbar machen

  • CSS Selektoren
  • Marker für Ajax

Tests mit festem DB Stand

  • Dedizierte Daten je Test zum Verändern
  • Parallelisierbar

Wenig doppelt testen

50% Testabdeckung (Lines) über Unittests ggf. OK !?!

100% der Logik

Wenig doppelt testen

Integrationtests > 75%

Wenig doppelt testen

Kommuliert > 90%

Automatische Tests

sind die immer gültige Dokumentation
der Anforderungen an das System

Testautomatisierung sollte
in der Entwicklug geschehen!

Mit Unterstützung der QA!

Vertrauen schaffen!

Stefan Hildebrandt - consulting.hildebrandt.tk

  • Beratung, Coaching und Projektunterstützung
  • Java EE
  • Buildsysteme gradle und maven
  • Testautomatisierung
  • Coach in agilen Projekten
  • DevOps