Was eine QS-Abteilung immer wollte...

Mit automatisierten Tests zum Ziel

Stefan Hildebrandt / @hildebrandttk

Qualitätsanforderung
an ein Softwaresystem

Alles funktioniert!!!

Nachfrage aus der Testabteilung (... alternativ PO, Fachabteilung):

Ist es schon getestet?

Antwort von der Entwicklung:

Wir haben mehr als 90% Testabdeckung und der Build ist grün

Nachfrage von der Fachseite:

Wo ist der Test für Feature 1: Anlage von Tierärzten?

Antwort von der Entwicklung:
  • VetServiceTest.testCreateNewVet*
  • VetValidationTest.test*
  • VetUiIT.test*, ...
  • ...
Nachfrage von der Fachseite:

Was wird dort genau getestet?

Kann ich das irgendwo sehen?

Antwort von der Entwicklung:

Klar

jUnit Report

Antwort von der Entwicklung:

@Test
@InSequence(6)
@RunAsClient
public void testNewVetPageWithSpecialties() {
  goTo(SpecialtiesPage.class);
  specialtiesPage.clickAddNewSpecialty();
  newSpecialtiesPage.addNewContent("dentist");
  specialtiesPage.clickAddNewSpecialty();
  newSpecialtiesPage.addNewContent("anesthetist");
  specialtiesPage.clickAddNewSpecialty();
  newSpecialtiesPage.addNewContent("radiology");
  goTo(VetsPage.class);
  vetsPage.assertPageIsLoaded();
  vetsPage.clickAddNewVet();
  newVetPage.assertPageIsLoaded();
  newVetPage.addNewContentWithAllSpecialties("Thomas", "Woehlke");
  vetsPage.assertPageIsLoaded();
  vetsPage.assertContentFoundWithSpecialties("Thomas", "Woehlke", "anesthetist dentist radiology");
}
Reaktion von der Fachseite:

Aha ???

Ende des Gesprächs!

Problem:

Technische

vs.

fachliche Sicht

Technische Sicht

  • Orientiert sich an der internen Struktur des Systems:
    • Komponenten
    • Klassen
    • Methoden
  • Viele Ebenen:
    • Unittests
    • Komponententests
    • Integrationstests
    • Systemintegrationstests

Fachliche Sicht

  • Orientierung an
    • Funktionsbereichen
    • Benutzeroberflächen
    • Geschäftsvorfällen
  • Ableitung aus Anforderungen:
    • Lasten- / Pflichtenheft
    • Userstories
    • Change Requests
    • ...
Klassisches Testvorgehen

Akzeptanztests

Akzeptanztests

  • Orientiert sich an fachlichen Anforderungen
  • Blackbox-Testing -> Test nur an externen Schnittstellen
    • UI
    • Externe-Services

Anforderungen

  • In "vertrauenswürdigen" Umgebungen
  • Nachvollziehbar
  • Entkoppelt / unabhängig
  • Aussagekräftig und verständlich
  • Redundanzfrei

Aufbau

  1. Vorbedingungen
  2. Aktionen
  3. Prüfung der Nachbedingungen

Typische Werkzeuge im Java Universum

Beispiel:

Cucumber Feature

Feature: Owner
   Scenario: Create new owner on find owner page
      Given browser on find owner page
      When create new owner Hans Wurst, living Meicastr. 6, Edewecht with phone 44059990
      Then owner Hans Wurst, living Meicastr. 6, Edewecht with phone 44059990 exists
   Scenario: Create new owner on find owner result page
      Given browser on find owner result page
      When create new owner Conchita Wurst, living Frankfurter Str. 6, Wien with phone 44059991
      Then owner Conchita Wurst, living Frankfurter Str. 6, Wien with phone 44059991 exists
  • Sollte in einem deutschsprachigem Projekt auf deutsch sein!

Step Definition

Given(~'^browser on find owner page$') { ->
   to(HelloPage)
      .toFindOwners()
}

Given(~'^browser on find owner result page$') { ->
   to(HelloPage)
      .toFindOwners()
      .searchForOwner('')
}

When(~'^create new owner (\\w*) (\\w*), living ([a-zA-Z0-9\\ \\.]*), (\\w*) with phone (\\d*)$') { String firstName, String lastName, String street, String city, String phone ->
   ((FindOwnersPage)page).openNewOwnersPage()
      .addNewOwner(firstName, lastName, street, city, phone)
}

Then(~'^owner (\\w*) (\\w*), living ([a-zA-Z0-9\\ \\.]*), (\\w*) with phone (\\d*) exists$') { String firstName, String lastName, String street, String city, String phone ->
   ((FindOwnersResultPage)page).assertOwnerPresent(firstName, lastName, street, city, phone)
}

Cucumber Report

alles paletti?

Vorgehen bei Blackbox Akzeptanztests

  1. Setup mittels UI
    • relativ selten Services
  2. Ausführen der Aktionen
  3. Prüfung der Ergebnisse

Positivtest 1

  1. Anlage eines neuen Kunden
  2. Anlage eines neuen Vertrages
  3. Warten bis Vertrag aktiv und änderbar
  4. Änderung auf gültigen neuen Vertrag starten
  5. Eingabe valider Werte
  6. Prüfen das Vertrag angelegt wurde

Negativtest 1.1.1 Validierung Name

  1. Anlage eines neuen Kunden
  2. Anlage eines neuen Vertrages
  3. Warten bis Vertrag aktiv und änderbar
  4. Änderung auf gültigen neuen Vertrag starten
  5. Eingabe valider Werte
  6. Löschen des Wertes im Feld Name
  7. Prüfen, dass Validierungsfehler angezeigt wird
  8. Behebung des Fehlers
  9. Prüfen das Vertrag angelegt wurde

Negativtest 1.1.2 Validierung Name

  1. Anlage eines neuen Kunden
  2. Anlage eines neuen Vertrages
  3. Warten bis Vertrag aktiv und änderbar
  4. Änderung auf gültigen neuen Vertrag starten
  5. Eingabe valider Werte
  6. Einsetzen eines zu langen Wertes in das Feld Name
  7. Prüfen, dass Validierungsfehler angezeigt wird
  8. Behebung des Fehlers
  9. Prüfen das Vertrag angelegt wurde

Negativtest 1.1.3 Validierung Name

  1. Anlage eines neuen Kunden
  2. Anlage eines neuen Vertrages
  3. Warten bis Vertrag aktiv und änderbar
  4. Änderung auf gültigen neuen Vertrag starten
  5. Eingabe valider Werte
  6. Einsetzen eines zu kurzen Wertes in das Feld Name
  7. Prüfen, dass Validierungsfehler angezeigt wird
  8. Behebung des Fehlers
  9. Prüfen das Vertrag angelegt wurde

...

Negativtest 1.76.23 Validierung IBAN

  1. Anlage eines neuen Kunden
  2. Anlage eines neuen Vertrages
  3. Warten bis Vertrag aktiv und änderbar
  4. Änderung auf gültigen neuen Vertrag starten
  5. Eingabe valider Werte
  6. Ersetzen der IBAN durch eine mit fehlerhafter Prüfsumme
  7. Prüfen, dass Validierungsfehler angezeigt wird
  8. Behebung des Fehlers
  9. Prüfen das Vertrag angelegt wurde

alles paletti?

Nochmal die Beschreibung

Abstimmung mit Daumen

  • Orientiert sich an fachlichen Anforderungen
  • Blackbox-Testing -> Test nur an externen Schnittstellen
    • UI
    • Externe-Services
  • In "vertrauenswürdigen" Umgebungen
  • Nachvollziehbar
  • Entkoppelt / Unabhängig
  • Aussagekräftig und verständlich
  • Redundanzfrei

The good parts

  • Fachliche Sicht

The ugly parts

  • Kosten / Nutzen

Kosten

  • Lange Laufzeit
  • Schwierige Wartung
    • Komplexe Setups für Testfälle gemeinsamen Umgebungen
    • Mangelhafte Stabilität
  • Redundante Tests in der Entwicklung notwendig

Nutzen

  • Komplette und aktuelle Dokumentation der Systemanforderungen
  • Starke Reduktion manueller Tests
  • Frühes Feedback
  • In der Entwicklung bei kritischen Änderungen im Branch nutzbar

Akzeptanztests zu dogmatisch?

Ursachen für Stabilitätsprobleme

  • Nichtexklusive Umgebung
  • Unzuverlässige UI Automatisierung
  • Ungeeignete Werkzeuge

Meine neuen Testumgebungen

Stages on Demand

  • Automatische Bereitstellung von Virtuellen Servern / Cloud Instanzen
  • Applikationsserver, Datenbanken, Umsysteme, ...
  • Tools

Beispiel: Vagrant

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.box = "base"
  config.vm.box_url = "https://oss-binaries.phusionpassenger.com/vagrant/boxes/latest/ubuntu-14.04-amd64-vbox.box"

  config.vm.define :database do |database|
    database.vm.network "private_network", ip: "192.168.50.5"
    database.vm.network :forwarded_port, guest: 3306, host: 13306
    database.vm.hostname = "database"

  end
  config.vm.define :appserver do |appserver|
    appserver.vm.network "private_network", ip: "192.168.50.4"
    appserver.vm.network "forwarded_port", guest: 22, host: 10022
    appserver.vm.network "forwarded_port", guest: 8080, host: 18080
    appserver.vm.network "forwarded_port", guest: 9990, host: 19990
    appserver.vm.network "forwarded_port", guest: 8787, host: 51325
    appserver.vm.hostname = "appserver"
  end

  config.vm.provision "puppet" do |puppet|
    puppet.manifests_path = "manifests"
    puppet.module_path = "modules"
    puppet.manifest_file  = "hc2014.pp"
  end
end

Testumgebung

  • Vollautomatisiertes Deployment aller Bestandteile!
  • Datenbankchanges automatisieren
  • Testcontainer

Arquillian - Testklasse

@RunWith(Arquillian.class)
public class Test01Specialties {

    @Deployment(testable = false)
    public static WebArchive createDeployment() {
        return Deployments.createSpecialtiesDeployment();
    }

    @ArquillianResource
    private URL deploymentUrl;

    @Test
    @InSequence(1)
    @RunAsClient
    public void testOpeningHomePage() {
        goTo(HelloPage.class);
        helloPage.assertTitle();
    }

Arquillian - Deployment

@Deployment(testable = true)
public static WebArchive createFullDeployment() {
   return importMavenDependencies()
      .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
      .addAsResource("tk/hildebrandt/javaee7/petclinic/cucumber/OwnerValidation.feature")
      .addClass(OwnerValidationStepDefinitions.class)
      .addClass(Owner.class).addClass(Pet.class);
}

public static WebArchive importMavenDependencies() {
   File[] deps = Maven.resolver().loadPomFromFile(POM_PATH).importRuntimeDependencies().resolve().
      withTransitivity().asFile();
   WebArchive war = null;
   try {
      war = ShrinkWrap.create(WebArchive.class)
         .addAsLibraries(deps);
   } catch (Exception e) {
      e.printStackTrace();
   }
   return war;
}

UI bedienbar machen

  • Dedizierte CSS Klassen für Typ und Funktion
    • Bei HTML5 Anwendungen ohnehin nicht unüblich
    • Designer werden sich auch freuen
    • Andere Elemente des Systems werden auch testbar gestaltet
  • Einfache Eingaben aktivieren

Beispiel CSS

Beispiel Richfaces Calendar

Test04Owner

Werkzeuge zur Ausführung von Tests

  • Gemeinsame SCM Nutzung
  • Ausführung auf / gegen beliebige Umgebungen
  • IE nicht als primärer Browser für Tests
    • Sehr schlechte remote Schnittstelle
    • Bringt für eine verhaltensbasierte Abnahme keine Vorteile
      • CSS Fehler werden nicht gefunden
      • Bei Bedarf jasmin / Quinit-tests für JavaScript

Laufzeit: Testsetup

  • Bereitstellung von Testobjekten wird meist über die Oberfläche gemacht
    • Sehr lange Laufzeit
  • Alternativen
    • Datenbankstand mit validen Testobjekten
        Testet ebenfalls die Datenmigration
    • Anlage von Testdaten mittels Services

Optimierung des Beispiels

  • Vorbereitete Kunden in der Datenbank
  • Einschränkung des Blackbox-Gedankens
    1. Basistests mit UI
    2. Details an interner Schnittstelle

Positivtest 1

  1. Kunde 123133 öffnen
  2. Änderung auf gültigen neuen Vertrag starten
  3. Eingabe valider Werte
  4. Prüfen das Vertrag angelegt wurde

Negativtest 1 Validierung

  1. Kunde 123134 öffnen
  2. Änderung auf gültigen neuen Vertrag starten
  3. Eingabe invalider Werte in alle Felder
  4. Prüfen, dass Validierungsfehler angezeigt wird
  5. Eingabe valider Werte
  6. Prüfen das Vertrag angelegt wurde

Validierungstest 1.1.1 Validierung Name

  1. Vertragsobjekt mit validen Werten erstellen
  2. Löschen des Wertes in das Feld Name
  3. Prüfung das Validierungsfehler mit passender Meldung in der Liste der Fehlermeldungen ist

Validierungstest 1.1.2 Validierung Name

  1. Vertragsobjekt mit validen Werten erstellen
  2. Einsetzen eines zu langen Wertes in das Feld Name
  3. Prüfung das Validierungsfehler mit passender Meldung in der Liste der Fehlermeldungen ist

...

Negativtest 1.76.23 Validierung IBAN

  1. Vertragsobjekt mit validen Werten erstellen
  2. Ersetzen der IBAN durch eine mit fehlerhafter Prüfsumme
  3. Prüfung das Validierungsfehler mit passender Meldung in der Liste der Fehlermeldungen ist

Definierter Datenbankstand

Cucumber Tests als "Unit-Test"

Feature: Owner
   Scenario: Valid Owner must have a lastName
      Given valid owner instance
      Given set lastName to null
      When validation is executed
      Then 1 Validation errors with messages lastName darf nicht leer sein are present
   Scenario: Valid Owner must have a lastName
      Given valid owner instance
      Given set firstName to null
      When validation is executed
      Then 1 Validation errors with messages firstName darf nicht leer sein are present
   Scenario: Valid Owner must have a lastName
      Given valid owner instance
      Given set city to null
      When validation is executed
      Then 1 Validation errors with messages city darf nicht leer sein are present

Cucumber Tests als "Unit-Test"

   @Inject
   private Validator validator;
   private Owner owner;

   private Set<ConstraintViolation<Owner>> constraintViolations;
               @Given("^valid owner instance$")
               public void valid_owner_instance() throws Throwable {
               owner = new Owner();
               owner.setLastName("Wurst");
               owner.setFirstName("Hans");
               owner.setAddress("Some street");
               owner.setCity("some city");
               owner.setTelephone("0800666666");
               }

               @Given("^set (.*) to null$")
               public void setFieldWithNameToValue(String field) throws IllegalAccessException, NoSuchFieldException {
               final Field declaredField = owner.getClass().getDeclaredField(field);
               declaredField.setAccessible(true);
               declaredField.set(owner, null);
               }

               @When("^validation is executed$")
               public void validation_is_executed() throws Throwable {
               constraintViolations = validator.validate(owner);
               }
               @Then("^(\\d*) Validation errors with messages (.*) are present$")
               public void validation_error_with_message_is_present(int count, String messages) throws Throwable {
               for (ConstraintViolation<Owner> constraintViolation : constraintViolations) {
                  final String pathWithMessage = constraintViolation.getPropertyPath() + " " + constraintViolation.getMessage();
                  assertTrue(pathWithMessage, messages.contains(pathWithMessage));
                  }
                  }

Arquillian: Suite Deployment

  • Für Integrationstests ist das Default-Verhalten: ein Deployment je Testklassenausführung ungeeignet
  • Suite Deployment
    • Benötigt Maven: arquillian-transaction-* >= 1.0.1.Final
    • Funktioniert aktuell generell nicht mit dem TomEE

Parallele Ausführung von Tests

  • Steht im Konflikt zu Arquillian
  • z.B. Forge Mode für die JUnit-Ausführung in Maven/Gradle
  • Ausnutzung der Multicore-Architekturen
  • Risiko von Threadingproblemen in der Testinfrastruktur
  • Tests müssen auf unabhängigen Daten arbeiten
  • Alternative: Parallele Zweige in der Buildpipiline

Optimierungspotential

  • Verbesserung des Prozesses der Anforderungsdokumentation
  • Generierung von Dokumentation auf Basis von Testautomatisierungselementen

Anforderungen weiterschreiben

  • Verlinkung an Tests in Richtung der UserStories
  • Tests als lebendes Anforderungsdokument inkl. WARUM

Zusammenfassung

  • Kombination von Testen auf allen Ebenen
  • Testen in stabilen, realitätsnahen Umgebungen
  • Exklusive Nutzung der Umgebungen
  • Beibehaltung der fachlichen Sicht!
  • Verstärkung der Kopplung zur Fachseite

Weiterführender Links:

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