|
|
(5 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) |
Zeile 1: |
Zeile 1: |
| [[Category:Java]] | | [[Category:Java]] |
| | [[Category:Ausbildung]] |
| | __NOTOC__ |
| | = Unit-Tests schreiben = |
| Das Schreiben von Unit-Tests ist eine einfache Sache, es lohnt sich aber das an kleinen Beispielen zu üben bis es locker aus dem Handgelenk kommt. Unit-Tests haben viele Aspekte und Facetten, wir fangen mal mit dem einfachsten Fall an: Testen einer einzelnen Funktion/Methode. | | Das Schreiben von Unit-Tests ist eine einfache Sache, es lohnt sich aber das an kleinen Beispielen zu üben bis es locker aus dem Handgelenk kommt. Unit-Tests haben viele Aspekte und Facetten, wir fangen mal mit dem einfachsten Fall an: Testen einer einzelnen Funktion/Methode. |
|
| |
|
| Wähle zunächst eine einfache Funktion. Sie sollte überschaubar sein, aber eine Reihe unterschiedlicher Varianten haben die sich zu testen lohnen. Zum Beispiel:
| | == Funktion für das Tutorial wählen == |
| *Umrechnen von Temperaturen z.B. Celsius-Fahrenheit
| | Für das Tutorial wähle zunächst eine einfache Funktion. Sie sollte überschaubar sein, aber eine Reihe unterschiedlicher Varianten haben die sich zu testen lohnen. Als Beispiel nehmen wir hier die Berechnung des arithmetischen Mittels (aka der Durchschnitt) einer Reihe von Zahlen. |
| *Berechnen der Quersumme einer Zahl
| |
| *Fibonacci-Zahle
| |
| *Durchschnitt von n Zahlen
| |
|
| |
|
| | Um es einfacher zu machen, sind für die Eingabe nur ganze Zahlen erlaubt. Das Ergebnis ist in der Regel keine ganze Zahl mehr. |
| | |
| | Die zu implementierende Methode habe die Signatur |
| | {{java|code=float schnitt(int[] zahlen);}} |
| | oder |
| | {{java|code=float schnitt(int... zahlen);}} |
| | |
| | == Funktion implementieren == |
| Schreibe nun eine Klasse mit einer Methode die die Funktion berechnet. | | Schreibe nun eine Klasse mit einer Methode die die Funktion berechnet. |
|
| |
|
| Normalerweise schreibt man Unit-Tests während man die Funktionalität entwickelt, das ist in dieser Übung aber nicht wichtig. | | Normalerweise schreibt man Unit-Tests während man die Funktionalität entwickelt. |
| Du kannst die Funktionalität auch durchimplementieren bis Du meinst, daß alles funktioniert und dann die Tests schreiben.
| | Da für dieses Tutorial die Unit-Tests im Fokus stehen, ignorieren wir das mal. |
| | Die Implementierung darf also bereits erfolgt sein. |
|
| |
|
| | == Unit-Tests schreiben == |
| Lege nun eine Unit-Test-Klasse an und schreibe Tests dazu. | | Lege nun eine Unit-Test-Klasse an und schreibe Tests dazu. |
| Lies dazu die Abschnitte "Regeln..." und "Aufbau..." im Spickzettel Unit-Test | | Lies dazu die Abschnitte "Regeln..." und "Aufbau..." in [[Gestaltung von Unit-Tests|Gestaltung von Unit-Test]] |
|
| |
|
| Frage Dich dabei | | Frage Dich dabei |
Zeile 22: |
Zeile 32: |
| * Woher bekomme ich Testdaten, was ist dabei wichtig? | | * Woher bekomme ich Testdaten, was ist dabei wichtig? |
|
| |
|
| | = Das wirkliche Leben = |
| Unit-Tests nach Abschluß der Implementierung zu schreiben ist nicht empfehlenswert. | | Unit-Tests nach Abschluß der Implementierung zu schreiben ist nicht empfehlenswert. |
| Zunächst einmal hat dazu kein Enwickler lust. Dementsprechend wird die Qualität –- | | Zunächst einmal hat dazu (fast) kein Entwickler Lust, sich nach der Implementierung noch |
| wenn den überhaupt sinnvolle Tests geschrieben werden.
| | mit einem Code zu beschäftigen. |
| Vor allem aber hat das Programm die Tendenz sich so zu entwickeln, daß es sich gar nicht richtig testen läßt. | | |
| | Vor allem aber hat ein Programm ohne Unit-Tests die Tendenz sich so zu entwickeln, |
| | daß es sich gar nicht richtig testen läßt. Unser Beispiel ist ist einfach und überschaubar. "Echte" Software wird im Laufe der Zeit erweitert und angepaßt. Oft genug wird der Code dann irgendwo reingehackt; die entstehenden Methoden werden sehr schnell sehr komplex und sind dann nicht mehr richtig zu testen. |
|
| |
|
| Deshalb ist es empfehlenswert, Unit-Tests während der Entwicklung zu schreiben. | | Deshalb ist es empfehlenswert, Unit-Tests ''während'' der Entwicklung zu schreiben. |
| Das ist auch die Voraussetzung für modernere Programmiertechniken wie agile Entwicklung und TDD. | | Das ist auch die Voraussetzung für modernere Programmiertechniken wie agile Entwicklung und TDD. |
| | | Ein paar Anmerkungen dazu finden sich [[Testgetriebene Entwicklung|hier]]. |
| '''Aufgabe:''' Umrechnung römischer in arabische Zahlen
| |
| | |
| Wir beschränken uns auf die (beliebige) Reihung von Ziffern-Zeichen die addiert werden.
| |
| Die Ziffern sind: : I = 1 V = 5 X = 10 L = 50 C = 100 D = 500 M = 1000
| |
| Bsp: MCCCLXV = 1000 + 3 * 100 + 50 + 10 + 5 = 1365
| |
| | |
| Die Reihenfolge sei zunächst egal: IV = VI = 6
| |
| | |
| '''Der Projekt-Plan'''
| |
| | |
| Wir gehen die Aufgabe iterativ an. In jeder Runde fügen wir ein neues Feature hinzu und decken dabei alle Erweiterungen durch Tests ab.
| |
| Im Vordergrund steht nicht die Lösung der Aufgabe, sondern das Vorgehen bei der Entwicklung:
| |
| <q>Sei Dir bei jedem Schritt bewußt, was Du tust und warum Du es tutst.</q>
| |
| Halte Dich an den Plan, auch wenn Du am liebsten alles auf einmal umsetzen möchtest (Lächeln)
| |
| | |
| Folgende Iterationen sind vorgesehen:
| |
| | |
| ;einzelne Ziffern umrechnen
| |
| :Nacheinander wird jede einzelne Ziffer umgerechnet: I V X L C D M
| |
| :Die Iteration ist abgeschlossen, wenn das Programm alle sieben Ziffern umrechnen kann.
| |
| ;Zahlen mit einer festen Zahl (größer eins) von Ziffern umrechnen
| |
| :Nacheinander werden Folgen von Ziffern umgewandelt. Beginne mit zwei Ziffern.
| |
| :Wenn das Programm für alle 2-Ziffern-Kombination funktioniert wende Dich 3-Ziffern-Kombinationen zu und so fort.
| |
| :Überlege Dir dabei, wie Du Dein Programm für beliebig lange Folgen erweitern kannst.
| |
| :Gehe zur nächsten Iteration, wenn Du so weit bist.
| |
| ;Zahlen beliebiger Länge umrechnen
| |
| :Das Programm soll nun Zahlenfolgen beliebiger Länge umrechnen können.
| |
| ;Differenzen berücksichtigen (optional)
| |
| :Wenn Du noch nicht genug hast, überlege, wie Du die Differenz-Regel umsetzen kannst.
| |
| :Dabei werden kleinere Ziffern, die links einer Größeren stehen abgezogen:
| |
| : IV = 4, IX = 9
| |
| | |
| '''Vorgehen'''
| |
| | |
| Das hier beschriebene Vorgehen wird auch als "Test First" bezeichnet, weil immer ''erst'' der Test und
| |
| '''anschließend''' die Implementierung geschrieben wird.
| |
| | |
| Zur Vorbereitung legst Du eine Klasse mit einer Methode für die Umrechnung an.
| |
| Konzentriere Dich auf den jeweiligen Schritt der jeweiligen Iteration. Die erste Methode muß nicht
| |
| mit Strings arbeiten, sondern darf auch Zeichen vom Typ {{java|char}} umrechnen.
| |
| Es ist normal, daß Schnittstellen im Laufe der Entwicklung geändert oder erweitert werden.
| |
| | |
| Als nächstes legst Du dazu eine Test-Klasse an und schreibst den ersten Unit-Test für den ersten
| |
| Schritt der ersten Iteration. Das Vorgehen wird für jeden Schritt jeder Iteration gleich sein:
| |
| | |
| *Schreibe einen Test der den Schritt kontrolliert<br>Der Test ist rot
| |
| *Erweitere Deine Implementierung um die Funktionalität<br>Der Test wird grün
| |
| *Stelle sicher, daß alle anderen Tests grün sind
| |
| | |
| So verfährst Du bis alles fertig ist.
| |
| | |
| Variation Test Driven Development (TDD)
| |
| | |
| Durch Hinzufügen eines weiteren Schritts kann man das Verfahren zu TDD ausweiten.
| |
| | |
| Gerät durch das sture Hinzufügen einzelner Features der Blick auf das Gesamtprogramm aus dem Fokus,
| |
| wird das Endergebnis als ein Haufen zusammengeworfener Methoden und Klassen dastehen.
| |
| Zwar garantieren die Tests, daß das Programm tut was es soll, aber der arme Mensch der das später erweitern soll
| |
| wird keine Freude daran haben.
| |
| | |
| Halte nach jedem Schritt (bzw. nach jedem grün gewordenen Test) inne und sieh Dir das Gesamtprodukt an.
| |
| Überlege Dir:
| |
| *Kann ich etwas besser machen?
| |
| *Kann ich etwas zusammenfassen?
| |
| *Kann ich etwas verallgemeinern?
| |
| Setze Maßnahmen, die Dir sinnvoll scheinen um und überprüfe das Ergebnis anhand der Unit-Tests.<br>
| |
| Wichtig dabei ist:
| |
| {{quotation|Die Änderungen dürfen keine Änderungen an der bestehende Funktionalität mit sich bringen}}
| |
| | |
| Dieses Vorgehen –- man nennt es Refactoring –- erfordert Übung und Erfahrung.
| |
| Zögere nicht, Kollegen hinzuzuziehen, wenn Du damit Schwierigkeiten hast oder Anregungen brauchst.
| |
| | |
| Frage Dich:
| |
| *Wozu dienen Unit-Tests?
| |
| *Was gehört zu einem Test?
| |
| *Was macht einen guten Test aus?
| |
| | |
| Im Folgenden sind einige Konventionen für das Schreiben von Unit-Tests zusammengestellt.
| |
| Es gibt viele verschiedene Meinungen und Ansätze an Unit-Tests heranzugehen; die folgenden Regeln haben sich
| |
| in der Praxis als eine einfache Variante bewährt um schnell vorwärts zu kommen.
| |
| | |
| Wichtig ist, daß man sich für ein Vorgehen entscheidet und das dann
| |
| möglichst konsequent durchzieht, am Besten in Absprache mit dem Entwicklungs-Team mit dem man gerade arbeitet.
| |
| Merke: Der Unit-Test soll den Entwickler unterstützen und jedem helfen der sie liest.
| |
| Regeln für Test-Klassen und Test-Methoden
| |
| | |
| Für jede zu testende Klasse wird eine eigene Test-Klasse mit dem Zusatz Test erstellt,
| |
| Zur Klasse {{java|TemperaturRechner}} erstellt man eine Klasse {{java|TemperaturRechnerTest}}
| |
| Alle Test-Methoden haben die gleiche Form (Kein Prä- oder Suffix "Test" am Methodennamen)
| |
| {{java|code=@Test public void xx(){...} }}
| |
| Der Bezeichner der Test-Methoden soll in einem Satz beschreiben was getestet wird
| |
| und was das erwartete Ergebnis ist. Getestet wird das Interface der zu testenden Klasse:
| |
| Alle Methoden mit Ausnahme der private-Methoden werden getestet. Gegebenenfalls kann für einzelne Methode der
| |
| zu testenden Klasse eine eigene Test-Klasse angelegt werden, wenn zu viele Tests zusammenkommen. Die
| |
| Test-Klasse heißt dann z.B. {{java|TemperaturRechnerCelsius2Fahrenheit}}
| |
| | |
| Hier ist ein einfaches Beispiel. Die Namensgebung ist hier etwas schwierig, da Methodennamen nicht mit Zahlen
| |
| beginnen dürfen. Da ist etwas Kreativität gefragt.
| |
| {{java|code=
| |
| @Test
| |
| public void fahrenheit32ErgibtCelsius0() {
| |
| TemperaturRechner rechner = new TemperaturRechner();
| |
| long celsius = rechner.f2c(32);
| |
| Assert.assertEquals(clesius, 0);
| |
| }
| |
| }}
| |
| '''Aufbau eines Unit-Tests
| |
| | |
| Ein Unit-Test hat in der Regel drei Schritte:
| |
| | |
| ;1. Test vorbereiten
| |
| :In diesem Schritt werden Voraussetzungen für den Test geschaffen, zum Beispiel Test-Objekte oder Daten erzeugt.
| |
| :in der Literatur wird der Versuchsaufbau auch als Fixture oder Setup bezeichnet.
| |
| :Hier werden ggf. auch Voraussetzungen dokumentiert.
| |
| :Wenn zum Beispiel ein nicht-leeres Objekt benötigt wird, kann man das mit einem Assert sicherstellen.
| |
| ;2. Test durchführen
| |
| :In diesem Schritt wird die zu testende Methode in "geeigneter Weise" aufgerufen.
| |
| ;3. Ergebnis prüfen
| |
| :Am Ende des Unit-Tests muß immer eine Prüfung stehen; ohne Assert ist der Test kein Test!
| |
| :Mit einem geeigneten Assert prüft man, ob das gewünschte Ergebnis eingetreten ist.
| |
| :Variationen
| |
| :Test-Methoden komprimieren
| |
| | |
| Die drei – im vorangegangenen Abschnitt aufgeführten – Schritte müssen nicht explizit von einander getrennt sein.
| |
| Kürzere Methoden sind oft leichter zu lesen als lange, erlaubt ist alles, was jeder andere Entwickler ohne Verrenkung verstehen kann.
| |
| Den Beispiel-Test kann man auch so zusammendampfen:
| |
| {{java|code=
| |
| @Test
| |
| public void fahrenheit32ErgibtzCelsius0() {
| |
| Assert.assertEquals(new TemperaturRechner().f2c(32), 0);
| |
| }
| |
| }}
| |
| '''Gemeinsame Test-Objekte'''
| |
| | |
| Die meisten Test-Klassen konzentrieren sich auf eine einzelne zu testende Klasse und benötigen oft nur
| |
| eine einzige Instanz. Ist sie – wie im Beispiel des Temperatur-Rechners – zustandslos, deklariert man
| |
| sie als Feld und verwendet sie einfach in allen Tests. Das spart jedesmal eine Deklaration. Als Bezeichner
| |
| kann man z.B. testInstanz oder testObjekt verwenden.
| |
| | |
| Gelegentlich wird sie auch als "subject under test" bezeichnet und abkürzend mit sut bezeichnet.
| |
| Solche Abkürzungen sollte man vermeiden, sie sind nicht professionell sondern verwirrend.
| |
| Der Beispiel-Test sieht dann so aus:
| |
| {{java|code=
| |
| public void TemperaturRechnerTest {
| |
| TemperaturRechner rechner = new TemperaturRechner();
| |
| @Test
| |
| public void fahrenheit32ErgibtzCelsius0() {
| |
| Assert.assertEquals(rechner.f2c(32), 0);
| |
| }
| |
| }
| |
| }}
| |
Unit-Tests schreiben
Das Schreiben von Unit-Tests ist eine einfache Sache, es lohnt sich aber das an kleinen Beispielen zu üben bis es locker aus dem Handgelenk kommt. Unit-Tests haben viele Aspekte und Facetten, wir fangen mal mit dem einfachsten Fall an: Testen einer einzelnen Funktion/Methode.
Funktion für das Tutorial wählen
Für das Tutorial wähle zunächst eine einfache Funktion. Sie sollte überschaubar sein, aber eine Reihe unterschiedlicher Varianten haben die sich zu testen lohnen. Als Beispiel nehmen wir hier die Berechnung des arithmetischen Mittels (aka der Durchschnitt) einer Reihe von Zahlen.
Um es einfacher zu machen, sind für die Eingabe nur ganze Zahlen erlaubt. Das Ergebnis ist in der Regel keine ganze Zahl mehr.
Die zu implementierende Methode habe die Signatur
float schnitt(int[] zahlen);
oder
float schnitt(int... zahlen);
Funktion implementieren
Schreibe nun eine Klasse mit einer Methode die die Funktion berechnet.
Normalerweise schreibt man Unit-Tests während man die Funktionalität entwickelt.
Da für dieses Tutorial die Unit-Tests im Fokus stehen, ignorieren wir das mal.
Die Implementierung darf also bereits erfolgt sein.
Unit-Tests schreiben
Lege nun eine Unit-Test-Klasse an und schreibe Tests dazu.
Lies dazu die Abschnitte "Regeln..." und "Aufbau..." in Gestaltung von Unit-Test
Frage Dich dabei
- Was sollen die Tests erreichen?
- Was muß getestet werden, welche Tests benötige ich?
- Wieviele Test sind nötig, wann habe ich genug getestet?
- Woher bekomme ich Testdaten, was ist dabei wichtig?
Das wirkliche Leben
Unit-Tests nach Abschluß der Implementierung zu schreiben ist nicht empfehlenswert.
Zunächst einmal hat dazu (fast) kein Entwickler Lust, sich nach der Implementierung noch
mit einem Code zu beschäftigen.
Vor allem aber hat ein Programm ohne Unit-Tests die Tendenz sich so zu entwickeln,
daß es sich gar nicht richtig testen läßt. Unser Beispiel ist ist einfach und überschaubar. "Echte" Software wird im Laufe der Zeit erweitert und angepaßt. Oft genug wird der Code dann irgendwo reingehackt; die entstehenden Methoden werden sehr schnell sehr komplex und sind dann nicht mehr richtig zu testen.
Deshalb ist es empfehlenswert, Unit-Tests während der Entwicklung zu schreiben.
Das ist auch die Voraussetzung für modernere Programmiertechniken wie agile Entwicklung und TDD.
Ein paar Anmerkungen dazu finden sich hier.