Qualitätsanforderung an ein Softwaresystem
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
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 ???
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
Anforderungen
In "vertrauenswürdigen" Umgebungen
Nachvollziehbar
Entkoppelt / unabhängig
Aussagekräftig und verständlich
Redundanzfrei
Aufbau
Vorbedingungen
Aktionen
Prüfung der Nachbedingungen
Typische Werkzeuge im Java Universum
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!
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)
}
Vorgehen bei Blackbox Akzeptanztests
Setup mittels UI
Ausführen der Aktionen
Prüfung der Ergebnisse
Positivtest 1
Anlage eines neuen Kunden
Anlage eines neuen Vertrages
Warten bis Vertrag aktiv und änderbar
Änderung auf gültigen neuen Vertrag starten
Eingabe valider Werte
Prüfen das Vertrag angelegt wurde
Negativtest 1.1.1 Validierung Name
Anlage eines neuen Kunden
Anlage eines neuen Vertrages
Warten bis Vertrag aktiv und änderbar
Änderung auf gültigen neuen Vertrag starten
Eingabe valider Werte
Löschen des Wertes im Feld Name
Prüfen, dass Validierungsfehler angezeigt wird
Behebung des Fehlers
Prüfen das Vertrag angelegt wurde
Negativtest 1.1.2 Validierung Name
Anlage eines neuen Kunden
Anlage eines neuen Vertrages
Warten bis Vertrag aktiv und änderbar
Änderung auf gültigen neuen Vertrag starten
Eingabe valider Werte
Einsetzen eines zu langen Wertes in das Feld Name
Prüfen, dass Validierungsfehler angezeigt wird
Behebung des Fehlers
Prüfen das Vertrag angelegt wurde
Negativtest 1.1.3 Validierung Name
Anlage eines neuen Kunden
Anlage eines neuen Vertrages
Warten bis Vertrag aktiv und änderbar
Änderung auf gültigen neuen Vertrag starten
Eingabe valider Werte
Einsetzen eines zu kurzen Wertes in das Feld Name
Prüfen, dass Validierungsfehler angezeigt wird
Behebung des Fehlers
Prüfen das Vertrag angelegt wurde
Negativtest 1.76.23 Validierung IBAN
Anlage eines neuen Kunden
Anlage eines neuen Vertrages
Warten bis Vertrag aktiv und änderbar
Änderung auf gültigen neuen Vertrag starten
Eingabe valider Werte
Ersetzen der IBAN durch eine mit fehlerhafter Prüfsumme
Prüfen, dass Validierungsfehler angezeigt wird
Behebung des Fehlers
Prüfen das Vertrag angelegt wurde
Mike Cohn veröffentlichte 2007 das Konzept der Testpyramide. Laut dieser Darstellung von Martin Fowler
sollen sich automatisierte Tests auf eine breite Basis schnell laufender und robuster Unittests
stützten.
... 15
Nach Oben hin sollen die Tests seltener werden, da sie länger laufen und labiler werden.
Nochmal die Beschreibung
Abstimmung mit Daumen
Orientiert sich an fachlichen Anforderungen
Blackbox-Testing -> Test nur an externen Schnittstellen
In "vertrauenswürdigen" Umgebungen
Nachvollziehbar
Entkoppelt / Unabhängig
Aussagekräftig und verständlich
Redundanzfrei
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 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
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
Basistests mit UI
Details an interner Schnittstelle
Positivtest 1
Kunde 123133 öffnen
Änderung auf gültigen neuen Vertrag starten
Eingabe valider Werte
Prüfen das Vertrag angelegt wurde
Negativtest 1 Validierung
Kunde 123134 öffnen
Änderung auf gültigen neuen Vertrag starten
Eingabe invalider Werte in alle Felder
Prüfen, dass Validierungsfehler angezeigt wird
Eingabe valider Werte
Prüfen das Vertrag angelegt wurde
Validierungstest 1.1.1 Validierung Name
Vertragsobjekt mit validen Werten erstellen
Löschen des Wertes in das Feld Name
Prüfung das Validierungsfehler mit passender Meldung in der Liste der Fehlermeldungen ist
Validierungstest 1.1.2 Validierung Name
Vertragsobjekt mit validen Werten erstellen
Einsetzen eines zu langen Wertes in das Feld Name
Prüfung das Validierungsfehler mit passender Meldung in der Liste der Fehlermeldungen ist
Negativtest 1.76.23 Validierung IBAN
Vertragsobjekt mit validen Werten erstellen
Ersetzen der IBAN durch eine mit fehlerhafter Prüfsumme
Prüfung das Validierungsfehler mit passender Meldung in der Liste der Fehlermeldungen ist
Definierter Datenbankstand
Automatische Provisionierung
DbUnit
Extraktion von Testdaten mit Jailer
Docker Container mit Datenbank
Einfach neue Schichten mit neuen Daten
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
Beratung, Coaching und Projektunterstützung
Java EE
Buildsysteme gradle und maven/ant-Migration
Testautomatisierung
Coach in agilen Projekten
DevOps