Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
Komponenten – WS 2014/15 – Teil 8/Mocks 15.11.14 1
Komponenten-basierte Entwicklung
Teil 8: Einführung in Mocks
Komponenten – WS 2014/15 – Teil 8/Mocks 2
Literatur und Web
[8-1] Vigenschow, Uwe: Objektorientiertes Testen und Testautomatisierung in der Praxis. dpunkt, 2005
[8-2] Westphal, Frank: Testgetriebene Entwicklung mit JUnit & FIT. dpunkt, 2006
[8-3] Link, Johannes: Softwaretests mit JUnit. 2. Auflage, dpunkt, 2005
[8-4] Tamm, Michael: JUnit-Profiwissen. dpunkt, 2013
[8-5] Ullenboom, C.: Java 7 - Mehr als eine Insel: Das Handbuch zu den Java SE-Bibliotheken, Gallileo Computing, 2011
Komponenten – WS 2014/15 – Teil 8/Mocks 3
Übersicht
• Unit-Tests
• Abhängigkeiten
• Arbeiten mit Stubs
• Arbeiten mit Mocks
Komponenten – WS 2014/15 – Teil 8/Mocks 4
Bedingungen für sinnvolles Testen
• Reproduzierbarkeit eines Testfalls
• Klar definierte Umgebung
• Zusammenfassung von– Daten in Dateien
– Zuständen von Datenbanken
– Erfassung des Outputs
zu einem Testfall.
• Es wird immer nur ein Aspekt getestet.
• Abdeckung des Codes:– Alle Pfade (Minimalbedingung)
– Alle möglichen Pfadkombinationen
müssen mindestens einmal durchlaufen werden
• Pfad = Codesequenz ohne Möglichkeit einer Verzweigung innerhalb einer Routine
Komponenten – WS 2014/15 – Teil 8/Mocks 5
Unit- und Integrations-Test
• Ein Unit-Test liegt nur dann vor, wenn ein kleines in sich abgeschlossenes Codestück sinnvoll getestet wird.
• Üblicherweise sind diese Codestücke– Einzelne Klassen in OO-Sprachen, z.B. Java, C#, C++
– Funktionen/Routinen, z.B. C
– Module, z.B. Modula, ADA (erste Version)
• Diese Codestücke sind dann die Units.
• Ein Integrationstest liegt vor, wenn mehrere Units zu einem Komplex zusammengefasst getestet werden.
• Es wird empfohlen, mit Unit-Tests zu beginnen und dann Integrationstest durchzuführen, wenn die betreffenden Units in Ordnung sind.
Siehe dazu
• http://de.wikibooks.org/wiki/Softwaretechnik:_Unit-Testing
• http://en.wikipedia.org/wiki/Unit_testing
• http://de.wikipedia.org/wiki/Modultest
Komponenten – WS 2014/15 – Teil 8/Mocks 6
Abhängigkeiten I
• Abhängigkeit = Dependency = Gerichtete Relation zwischen zwei Codestücken A und B, wobei die Korrektheit von A von der Korrektheit von B abhängt, in dem Sinne, dass A fehlerhaft ist, wenn B fehlerhaft ist. Dann hängt A von B ab.
• Aufgrund von Rekursion kann es auch eine gegenseitige Abhängigkeit geben.
• Statt von Abhängigkeit kann auch von der Benutzt-Relation gesprochen werden.
• Abhängigkeiten können wie folgt realisiert werden:– A ruft B auf (z.B. Call, Methodenaufruf)
– B produziert Daten für A (z.B. Daten, Messwerte)
– B ist der Manager für A (z.B. Starten, Stoppen, Scheduling)
– A erbt von B
– B interpretiert die Software von A (z.B. CPU, Virtuelle Maschine)
– B übersetzt die Software von A (z.B. Compiler, Assembler)
• Wir beschäftigen uns hier nur mit den Aufruf-Abhängigkeiten.
Komponenten – WS 2014/15 – Teil 8/Mocks 7
Beispiel
• Erstellt mit dem dependency-Walkerhttp://www.dependencywalker.com/
• http://www.heise.de/download/dependency-walker.html
Die Abhängigkeiten desJava-Compiler javac aufeinem x64-Win7 System
Komponenten – WS 2014/15 – Teil 8/Mocks 8
Abhängigkeiten II
• http://en.wikipedia.org/wiki/Coupling_(computer_programming)
• http://de.wikipedia.org/wiki/Dependency_Injection
Siehe dazu
Klasse AKlasse A
Klasse B
Call
Klasse AKlasse A
Stub B
Call
Wie könnte ein Unit-Test der Klasse A aussehen?
Klasse B ist vollständigrealisiert
Klasse B ist nur für den Testrealisiert (Simulation)
Komponenten – WS 2014/15 – Teil 8/Mocks 9
Stubs und Mocks I
• Stub = Codestück zur Bereitstellung von Schnittstellen, z.B. Schnittstellencode bei CORBA
• Stub = Codestück zur Simulation einer Schnittstelle/UnitDiese Variante des Begriffs ist hier gemeint
• Ein Stub-Objekt – oder kurz Stub – – ist eine verkürzte Implementierung eines anderen Objekts,
– liefert vorgefertigte Antworten/Reaktionen.
• http://de.wikipedia.org/wiki/Stub_(Programmierung)
• http://de.wikipedia.org/wiki/Mock-Objekt
• http://de.wikipedia.org/wiki/Mocking_Framework
Siehe dazu
Komponenten – WS 2014/15 – Teil 8/Mocks 10
Stubs und Mocks II
• Mock = Attrappe = Stub mit Monitormöglichkeiten
Darunter wird verstanden, dass die Aufrufe des zu testenden Objekts samt Parametern aufgezeichnet werden, um nach dem Test auf Korrektheit geprüft zu werden.
• Mock Objekte – kurz Mocks – werden fast immer mit Bibliotheken bzw. Frameworks realisiert.
• Beispiele dafür sind– EasyMock
– MockObjects
– PowerMock
– Mockito
Komponenten – WS 2014/15 – Teil 8/Mocks 11
Vorgehen I
Klasse AKlasse A
TestRunner
Klasse Atest
Stub B
Call
CUT benutzt nur Sprache undderen Standard-Libraries
Call
Klasse AKlasse A
TestRunner
Klasse Atest
Call
Call
Call
CUT benutzt nebenSprache dieKlasse B
Komponenten – WS 2014/15 – Teil 8/Mocks 12
Vorgehen II
• CUT = Class Under Test oder Component Under Test
• SUT = System Under Test
• DOC = Depended On Component
• Zu einem Testfall (also einem Test eines Aspekts) gehören:
Stub B
Klasse ACUT
Klasse CUTtest
Call
Call
Die Implementierungensind eng gekoppelt undsollten daher immerzusammen bleiben
SUT
DOC
Komponenten – WS 2014/15 – Teil 8/Mocks 13
Beispiel 1: Schnick-Schnack-Schnuck I
• Es soll ein Spielsimulator für das Spiel Schnick-Schnack-Schnuck oder Schere-Stein-Papier realisiert werden.
• Vorgehen:– Es beginnt mit einem Klassenentwurf.
– Dann werden Interfaces definiert.
– Darauf basierend eine unvollständige Klasse, die dann schrittweise getestet wird.
http://de.wikipedia.org/wiki/Schere,_Stein,_Papier
Komponenten – WS 2014/15 – Teil 8/Mocks 14
Beispiel 1: Schnick-Schnack-Schnuck II
• Es wird entschieden, keine enum-Klasse zu implementieren, stattdessen wird mit Konstanten gearbeitet.
SiSaSu
Oponent
MyChoice
Call
Call
DiceInterface
Moves
States
implements
extends
implements
implementsHauptprogramm
Spielgegner
Zufallsgenerator
Komponenten – WS 2014/15 – Teil 8/Mocks 15
Die Interfaces I
public interface States { final int tokenPaper = 0; final int tokenStone = 1; final int tokenScissors= 2; final int resultUndecided = 0; final int resultOponentWins = 1; final int resultHumanWins = 2;}
public interface Moves extends States { public int makeMove(int num); public int matchResult();}
erbt
Spielzüge
Spielergebnisse
Spielzug machen
Ergebnis berechnen
Komponenten – WS 2014/15 – Teil 8/Mocks 16
Die Interfaces II
• Wir nehmen nun an, dass die Klasse MyChoice bereits implementiert und getestet ist.
• Das geschah mit den vorher vorgestellten Methoden und Verfahren.
public interface DiceInterface { public int rollDice();}
import java.util.Random;
public class MyChoice implements DiceInterface { @Override public int rollDice() { Random rand = new Random(); return rand.nextInt(3); }}
generiert Zahlenzwischen 0 und 2
Komponenten – WS 2014/15 – Teil 8/Mocks 17
Leerschema der Klasse Oponent
public class Oponent implements Moves, States { Oponent() { } @Override public int makeMove(int humanTurn) { } @Override public int matchResult() { }}
Komponenten – WS 2014/15 – Teil 8/Mocks 18
Leerschema der Klasse OponentTest I
import static de.htw_berlin.f4.kbe.States.*;
public class OponentTest { Oponent opo; public OponentTest() {} @Before public void setUp() { opo= new Oponent(); } @After public void tearDown() { opo= null; }
@Test public void testPaper() { int choiceOfOponent= opo.makeMove(tokenPaper); int result= opo.matchResult(); assertEquals("Paper:Paper",resultUndecided,result); }}
Komponenten – WS 2014/15 – Teil 8/Mocks 19
Leerschema der Klasse OponentTest II
• Damit wir in der Testklasse und auch im Hauptprogramm die Konstanten für die Zustände benutzen können, müssen wir diese importieren (über ein Static Import).
• Jetzt wird auch klar, warum wir ein extra Interface mit den Konstanten benötigen (States): diese werden an verschiedenen Stellen benutzt.
• Hinweis: Die Trennung in zwei Interfaces ist eine Implemen-tierungsentscheidung, die auch anders hätte getroffen werden können, z.B. die Konstanten in das Interface Moves zu tun.
Nun, wenn wir dann die Klasse Oponent wie folgt implementieren,bekommen wir 2/3 aller Fälle einen roten Balken.
Komponenten – WS 2014/15 – Teil 8/Mocks 20
Klasse Oponent - Teil1
public class Oponent implements Moves { private int humanTurn; private int myTurn; private MyChoice dice; Oponent() { humanTurn= -1; myTurn = -1; dice= new MyChoice(); } @Override public int makeMove(int humanTurn) { this.humanTurn= humanTurn; myTurn= dice.rollDice(); return myTurn; }
Der Grund für den roten Balken liegt in der zufälligen Zahl durchMyChoice() - dies müsste geändert werden – geht aber nicht automatisch.
FesteVerdrahtung
Komponenten – WS 2014/15 – Teil 8/Mocks 21
Klasse Oponent – Teil2 - Spiellogik
@Override public int matchResult() { boolean humanWins= false; if(myTurn==humanTurn) { return resultUndecided; } switch(humanTurn) { case tokenPaper: humanWins= (myTurn==tokenStone); break; case tokenStone: humanWins= (myTurn==tokenScissors); break; case tokenScissors: humanWins= (myTurn==tokenPaper); break; } return humanWins ? resultHumanWins : resultOponentWins; }}
Komponenten – WS 2014/15 – Teil 8/Mocks 22
Ein Stub muss her I
• Die Benutzung einer anderen Klasse wird Dependency (Abhängigkeit) genannt.
• Wenn die Abhängigkeit von Außen gesteuert werden kann, wird dies Dependency Injection genannt.
• Hier erfolgt dies mit Hilfe des Konstruktors; es bleibt aber das Problem.
@Before public void setUp() { opo= new Oponent(new MyChoice());}
private DiceInterface dice;
Oponent(DiceInterface dice) { humanTurn= -1; myTurn = -1; this.dice= dice;}
Änderung in derKlasse Oponent(Konstruktor)
Änderung in derKlasse OponentTest(setUp())
Komponenten – WS 2014/15 – Teil 8/Mocks 23
Ein Stub muss her II
• Nun gibt es grün.
• Und wir können alle Testfälle definieren, die eine Wahl von Papier seitens der Maschine erfordern.
import static de.htw_berlin.f4.kbe.States.*;
public class StubPaper implements DiceInterface { @Override public int rollDice() { return tokenPaper; }}
@Before public void setUp() { opo= new Oponent(new StubPaper()); }
Das ist der Stub,der immer nurPapier wählt.
Das ist der Teilder Testklassemit dem Stub.
Komponenten – WS 2014/15 – Teil 8/Mocks 24
Ein Stub muss her III
public class OponentTest {... @Before public void setUp() { opo= new Oponent(new StubPaper()); }... @Test public void testPaper() { int choiceOfOponent= opo.makeMove(tokenPaper); int result= opo.matchResult(); assertEquals("Paper:Paper",resultUndecided,result); choiceOfOponent= opo.makeMove(tokenStone); result= opo.matchResult(); assertEquals("Paper:Stone",resultOponentWins,result); choiceOfOponent= opo.makeMove(tokenScissors); result= opo.matchResult(); assertEquals("Paper:Scissors",resultHumanWins,result);}}
Es werden alle drei Varianten, wenn die Maschine Papier wählt, getestet.
Komponenten – WS 2014/15 – Teil 8/Mocks 25
Ein Stub muss her IV
• Das Ganze wiederholt sich noch 2x mit jeweils einer Testklasse.
• Also 3x Oponent-Test und 3x Stub sind erforderlich.
• Dies lässt sich in einer Suite zusammenfassen:
package de.htw_berlin.f4.kbe;
import org.junit.runners.Suite;import org.junit.runner.RunWith;
@RunWith(Suite.class)@Suite.SuiteClasses({ OponentTest.class, Oponent2Test.class, Oponent3Test.class,})public class OponentSuite {}
Komponenten – WS 2014/15 – Teil 8/Mocks 26
Es läuft, aber...
• für die einzelnen Varianten jeweils manuell Stubs erzeugt werden.
• die Testklassen beim Aufbau des Fixture sehr ähnlich sind.
es ist recht unbefriedigend, da
Besser wäre es, wenn die Stubs automatisch generiert werden,und das machen Mock-Bibliotheken.
Im folgenden wird das Framework Mockito vorgestellt.
Komponenten – WS 2014/15 – Teil 8/Mocks 27
Mocks am Beispiel Mockito I - Installation
• http://mvnrepository.com/artifact/org.mockito/mockito-all/1.9.5
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.5</version></dependency>
• http://de.wikipedia.org/wiki/Mockito
• https://code.google.com/p/mockito/
• https://code.google.com/p/mockito/wiki/FAQ
• http://mockito.github.io/mockito/docs/current/org/mockito/Mockito.html
Maven
Allgemeine Information
Komponenten – WS 2014/15 – Teil 8/Mocks 28
Mocks am Beispiel Mockito II - netbeans
• Jar-Datei herunter laden, z.B. Version 1.9.5
• In Installationsordner kopieren, z.B. dort, wo auch maven ist
• In das Projekt bringen:
• Fertig!
Komponenten – WS 2014/15 – Teil 8/Mocks 29
Mocks Strategie
• Erwartungen definieren – welche Funktionen was tun
• Code ausführen und dabei Antworten aufzeichnen
• Reaktionen prüfen
• Aufzeichnung prüfen
Vier Phasen/Abschnitte
• Arrange
• Act
• Assert and Verify
Test werden am besten nach dem AAA-Schema geschrieben
Komponenten – WS 2014/15 – Teil 8/Mocks 30
SiSaSu – noch einmal mit Mockito I
public class Mocks02Test { public Mocks02Test() { }
@Test public void testPaper() { // Arange DiceInterface dice= mock(MyChoice.class); when(dice.rollDice()).thenReturn(tokenPaper); Oponent cut= new Oponent(dice);
// Act int choiceOfOponent= cut.makeMove(tokenPaper); int result= cut.matchResult();
// Assert and Verify assertEquals("Paper:Paper",resultUndecided,result); verify(dice,times(1)).rollDice(); }}
Der Aufbau dieser Testroutine arbeitet nicht mehr mit @before bzw. @after.
Komponenten – WS 2014/15 – Teil 8/Mocks 31
Erläuterungen I
• mock() erzeugt dynamisch (also zur Laufzeit) eine neue Klasse mit dem Interface des übergebenen Klassendeskriptors.
• Ein Klassendeskriptor wird per Reflektion erzeugt, hier durch das „Pseudo-Attribut" .class.
• Die Mock-Klasse (dice) wird mit dem Interface deklariert, um auszudrücken, dass nur gegen dieses Interface getestet wird.
DiceInterface dice= mock(MyChoice.class);
Komponenten – WS 2014/15 – Teil 8/Mocks 32
Erläuterungen II
when(dice.rollDice()).thenReturn(tokenPaper);
• Das ist eine Definition des Verhaltens des Mock-Objekts dice.
• rollDice() gibt den Wert tokenPaper zurück.
• Von dieser Angabe kann es beliebig viele Spezifikationen geben.
verify(dice,times(1)).rollDice();
• Hier wird die Aufzeichnung geprüft.
• rollDice() soll nur ein einziges Mal (times(1)) aufgerufen worden sein.
Bedingung Aktion
Komponenten – WS 2014/15 – Teil 8/Mocks 33
Fluent Interfaces I
• Mit Punkt verknüpfte Ketten von Methoden-Aufrufen werden Fluent Interfaces genannt.
• Diese sind sehr beliebt, weil sie sehr kompakt sind.
• Implementiert werden sie, indem am Ende der Methode immer return this programmiert wird.
• Wenn die Namen der Methoden sinnvoll gewählt sind, entsteht eine „neue Sprache"; diese Idee führt dann zu den DSL, den Domain Specific Languages.
Object.methode(...).methode(...).methode(...) usw.
• http://de.wikipedia.org/wiki/Fluent_Interface
• http://martinfowler.com/bliki/FluentInterface.html
Siehe
Komponenten – WS 2014/15 – Teil 8/Mocks 34
Fluent Interfaces II - Law of Demeter
• Fluent Interfaces verletzen das „Law of Demeter" (LoD):
Objekte sollen nur mit Objekten ihrer unmittelbaren Umgebung „kommunizieren", d.h. es soll immer eine lose Kopplung zwischen Komponenten/Klassen bestehen.
• Konkret: Eine Methode meth der Klasse C soll nur– klasseneigene Methoden
– Methoden der Parameter vom meth-Aufruf
– Mit C assoziierte Objekte, auch aus der Vererbung
– Methoden selbst erzeugter Objekte
aufrufen bzw. benutzen bzw. eine Abhängigkeit eingehen.
• Ziel: bessere Verständlichkeit und Wartbarkeit
• http://de.wikipedia.org/wiki/Gesetz_von_Demeter
• http://en.wikipedia.org/wiki/Law_of_Demeter
Siehe
Kritik
Komponenten – WS 2014/15 – Teil 8/Mocks 35
SiSaSu – noch einmal mit Mockito II
package de.htw_berlin.f4.kbe;
import static de.htw_berlin.f4.kbe.States.*;import static org.mockito.Mockito.*;import org.junit.Test;import static org.junit.Assert.*;
Der fehlende Testklassenkopf
verify(dice,times(1)).rollDice();
verify(dice,times(2)).rollDice();
Komponenten – WS 2014/15 – Teil 8/Mocks 36
SiSaSu – noch einmal mit Mockito III
@Test public void testPaper() { int choiceOfOponent, result; DiceInterface dice= mock(MyChoice.class); when(dice.rollDice()).thenReturn(tokenPaper); Oponent cut= new Oponent(dice); choiceOfOponent= cut.makeMove(tokenPaper); result= cut.matchResult(); assertEquals("Paper:Paper",resultUndecided,result); choiceOfOponent= cut.makeMove(tokenStone); result= cut.matchResult(); assertEquals("Paper:Stone",resultOponentWins,result); choiceOfOponent= cut.makeMove(tokenScissors); result= cut.matchResult(); assertEquals("Paper:Scissors",resultHumanWins,result); verify(dice,times(3)).rollDice();}
So, unddas 3x inleichtenVariationen
Komponenten – WS 2014/15 – Teil 8/Mocks 37
SiSaSu – noch einmal mit Mockito IV
private DiceInterface dice;private Oponent cut;
private void computerChoice(int token) { dice= mock(MyChoice.class); when(dice.rollDice()).thenReturn(token); cut= new Oponent(dice);}private void humanChoiceResult(int humanToken, String msg, int estimate) { int choiceOfOponent= cut.makeMove(humanToken); int result= cut.matchResult(); assertEquals(msg,estimate,result);}
• Der gemeinsame Teil wird in zwei eigene private(!) Routinen herausgezogen.
• Beide kommunizieren über private globale Variablen (nicht schön, aber einfach).
• Dadurch reduziert sich die Testzeilenzahl erheblich.
Komponenten – WS 2014/15 – Teil 8/Mocks 38
SiSaSu – noch einmal mit Mockito V
Die Tests sind nun kompakter, aber auch etwas schwerer lesbar.
@Test public void testPaper() { computerChoice(tokenPaper); humanChoiceResult(tokenPaper, "Paper:Paper", resultUndecided); humanChoiceResult(tokenStone, "Paper:Stone", resultOponentWins); humanChoiceResult(tokenScissors,"Paper:Scissors",resultHumanWins); verify(dice,times(3)).rollDice();}
@Test public void testStone() { computerChoice(tokenStone); humanChoiceResult(tokenStone, "Stone:Stone", resultUndecided); humanChoiceResult(tokenScissors,"Stone:Scissors",resultOponentWins); humanChoiceResult(tokenPaper, "Stone:Paper", resultHumanWins); verify(dice,times(3)).rollDice();}
Komponenten – WS 2014/15 – Teil 8/Mocks 39
DRY und Refactoring
• Refactoring = Verfahren der Reorganisation von Code mit den folgenden Zielen:– Bessere Verständlichkeit
– Bessere Wartbarkeit durch Abstraktion
• Refactoring hat nicht das Ziel der Optimierung von Code oder Laufzeit.• Siehe: http://www.ssw.uni-
linz.ac.at/Teaching/Lectures/Sem/2002/reports/Stelzmueller/index.html
• Das andere Prinzip heißt Dont Repeat Yourself (DRY).
• DRY-Prinzip = Vermeidung von duplizierten Code (entstanden durch cut&paste) und von sehr ähnlichem Code
• Ein Implementieren nach dem DRY-Prinzip führt zu denselben Zielen wie das Refactoring.
• Siehe: http://de.wikipedia.org/wiki/Don't_repeat_yourself
Komponenten – WS 2014/15 – Teil 8/Mocks 40
(Bad) Code smells – schlechter Code I
• Code Smells = Anzeichen von schlecht strukturierten, unverständlichen Code
• Hier wurden Konstanten innerhalb eines Interfaces definiert. Das ist ein Bad Code Smell.
• Siehe: http://wickedsource.org/2009/02/24/code-smell-constants-interfaces/
• Siehe:– http://de.wikipedia.org/wiki/Smell_(Programmierung)
– http://c2.com/cgi/wiki?CodeSmell
Komponenten – WS 2014/15 – Teil 8/Mocks 41
(Bad) Code smells – schlechter Code II
Die Konstanten gehören zur Schnittstelle; sie sind integraler Bestandteil; das ändert sich auch nicht nach dem Befolgen der Empfehlungen: – Ein Auslagerung in eine Klasse, von der die Konstanten geerbt werden,
bringt nichts, da Mehrfachvererbungen nicht möglich sind.
– Am besten ist die Benutzung zweier enum-Klassen, aber das macht wegen der grausamen Realisierung von Enums in Java alles komplizierter.
public interface States { final int tokenPaper = 0; final int tokenStone = 1; final int tokenScissors= 2; final int resultUndecided = 0; final int resultOponentWins = 1; final int resultHumanWins = 2;}
Böse, böse!
Typisch C-Programmierer!
Es gibt aber Argumente dafür:
Komponenten – WS 2014/15 – Teil 8/Mocks 42
Ein zweites komplexeres Beispiel I
• Es soll ein UPN-Taschenrechner mit den Grundrechenarten implementiert werden.
• UPN = Umgekehrte Polnische Notation
• Beispiel: "2" "3" "4" "+" "*" = 2*(3+4) = 14
• Entwurf:
Klasse AKlasse FixedLengthStack
Klasse UPN
Call
Hierfür wirdspäter einMock gebaut.
Komponenten – WS 2014/15 – Teil 8/Mocks 43
Ein zweites komplexeres Beispiel II - Maven
(1)
(3)
(2)
Etwas schneller – ohne das Herunterladender Archetypenbibliothek: POM-Project
Komponenten – WS 2014/15 – Teil 8/Mocks 44
Ein zweites komplexeres Beispiel III - Maven
• JUnit 4.11
• maven-jar-plugin – mit richtigem Name für Startklasse
• exec-maven-plugin – mit richtigem Name für Startklasse
• Mockito:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.5</version></dependency>
Komponenten – WS 2014/15 – Teil 8/Mocks 45
Vorgehen nach Test Driven Development (TDD)
• Rahmen der Klasse FixedLengthStack schreiben
• Testfälle realisieren
• Klasse implementieren public class FixedLengthStack { String field[]; int top; FixedLengthStack(int size) {
} public void push(String obj) { } public String pop() { return null; }}
public class FixedLengthStackTest { public FixedLengthStackTest() { } FixedLengthStack stack; @Before public void setUp() { stack= new FixedLengthStack(3); } @After public void tearDown() { stack= null; } @Test public void test01() { String ret; stack.push("abc"); ret= stack.pop(); assertEquals("1 Element -->","abc",ret);}}
Komponenten – WS 2014/15 – Teil 8/Mocks 46
Klasse FixedLengthStackTest I
...@Test public void test1Element() { String ret; try { stack.push("abc"); ret= stack.pop(); assertEquals("1 Element -->","abc",ret); } catch (StackOverflowException | StackUnderflowException ex) { ex.printStackTrace(); fail("unexpected exception occurred"); }}...
Nach diesem Schema werden 2 und 3 Elemente bei einer maximalenLänge von 3 getestet.
Kommentar
Erwartet
Berechnet
Komponenten – WS 2014/15 – Teil 8/Mocks 47
Klasse FixedLengthStackTest II
@Test public void testUnderflow() { String ret; try { ret= stack.pop(); fail("expected StackUnderflowException missed"); } catch (StackUnderflowException ex) { }}
@Test public void testOverflow() { try { stack.push("abc"); stack.push("def"); stack.push("hig"); stack.push("ZZZ"); fail("expected StackOverflowException missed"); } catch (StackOverflowException ex) { }}
So sehen Tests aus, die eineException erwarten
Komponenten – WS 2014/15 – Teil 8/Mocks 48
Klasse FixedLengthStack - Implementierung
public class FixedLengthStack { String field[]; int top; FixedLengthStack(int size) { field= new String[size]; top= -1; } public void push(String obj) throws StackOverflowException { if((top+1)>=field.length) { throw new StackOverflowException("push: Overflow"); } field[++top]= obj; } public String pop() throws StackUnderflowException { if(top<0) { throw new StackUnderflowException("pop: Underflow"); } return field[top--]; }}
Neuralgische Punkte!
Komponenten – WS 2014/15 – Teil 8/Mocks 49
Klasse UPN - Rahmen
public class UPN { FixedLengthStack stack; UPN(FixedLengthStack stack) { this.stack= stack; } private int executeOp(String op, int left, int right) throws IllegalFormedExpressionException { return 0; } public int compute(String expression[]) throws IllegalFormedExpressionException { return 0; }}
public class IllegalFormedExpressionException extends Exception { public IllegalFormedExpressionException() { } public IllegalFormedExpressionException(String msg) { super(msg); }}
Dependency
Injection
Komponenten – WS 2014/15 – Teil 8/Mocks 50
Klasse UPNMockTest I
import static org.mockito.Mockito.*;import org.junit.*;import static org.junit.Assert.*;import org.mockito.InOrder;
public class UPNMockTest { public UPNMockTest() { } UPN computer; FixedLengthStack stack; @Before public void setUp() { stack= mock(FixedLengthStack.class); computer= new UPN(stack); } @After public void tearDown() { computer= null; }
Injection
Komponenten – WS 2014/15 – Teil 8/Mocks 51
Klasse UPNMockTest II – Erste Version
@Test public void testAddition() { String expression[]= {"3","4","+"}; try { doNothing().when(stack).push("3"); doNothing().when(stack).push("4"); when(stack.pop()).thenReturn("4").thenReturn("3"); } catch (StackOverflowException | StackUnderflowException ex) {} try { int result= computer.compute(expression); assertEquals("3+4 -->",7,result); } catch (IllegalFormedExpressionException ex) { fail("unexpected exception occurred"); } try { InOrder inOrder = inOrder(stack); inOrder.verify(stack).push("3"); inOrder.verify(stack).push("4"); inOrder.verify(stack,times(2)).pop(); verifyZeroInteractions(stack); } catch (StackOverflowException | StackUnderflowException ex) {}}
Erwartungen
Prüfungen
Komponenten – WS 2014/15 – Teil 8/Mocks 52
Einfache dyadische Operationen I
@Test public void testAddition() { String expression[]= {"3","4","+"}; mockProlog2(expression,"7"); try { int result= computer.compute(expression); assertEquals("3+4 -->",7,result); } catch (IllegalFormedExpressionException ex) { fail("unexpected exception occurred"); } mockEpilog2(expression);}
Nach diesem Schema lassen sich auch die anderen dreiGrundrechenarten testen.Da es viele Wiederholungen gibt, werden diese in zweiRoutinen ausgelagert: mockProlog2() und mockEpilog2()
Komponenten – WS 2014/15 – Teil 8/Mocks 53
Einfache dyadische Operationen II
private void mockProlog2(String[] expr, String result) { try { doNothing().when(stack).push(expr[0]); doNothing().when(stack).push(expr[1]); when(stack.pop()).thenReturn(expr[1]) .thenReturn(expr[0]) .thenReturn(result); doNothing().when(stack).push(anyString()); } catch (StackOverflowException | StackUnderflowException ex) {}}
Beispiel: String expression[]= {"3","4","+"};
• Es werden die Reaktionen der simulierten Klasse definiert.
• Wenn unterschiedliche Resultate geliefert werden sollen, ist das Fluent-Interface zu benutzen.
Erwartungen
Komponenten – WS 2014/15 – Teil 8/Mocks 54
Erläuterungen I
doNothing().when(stack).push(expr[0]);
Hier wird eine void-Routine simuliert. Da keine Daten in derSimulation verwendet werden, wird auch nichts gemacht.
when(stack.pop()).thenReturn(expr[1]) .thenReturn(expr[0]) .thenReturn(result);
Hier wird festgelegt, in welcher Reihenfolge eine Funktion (pop)welche Rückgabewerte liefert.
when(stack.pop()).thenReturn(expr[1]);when(stack.pop()).thenReturn(expr[0]);when(stack.pop()).thenReturn(result);
Dies bewirkt etwas anderes: Für jeden Aufruf von pop() ohne Parameter wird result geliefert.Die vorherigen Werte werden nicht beachtet.
Komponenten – WS 2014/15 – Teil 8/Mocks 55
Erläuterungen II
doNothing().when(stack).push(anyString());
anyString() bedeutet, dass jeder Stringwert als Parametergemeint ist, d.h. es wird mindestens ein Aufruf von push(...)erwartet.Die ersten Aufrufe dazu sind wegen dieser Zeile redundant; siesollten aber trotzdem zum Verständnis geschrieben werden.
} catch (StackOverflowException | StackUnderflowException ex) {}
Das ist eine Verkürzung aus Java7: Die beiden Exceptions werdenmit Oder verknüpft.Dies ist dann erforderlich, wenn dieselbe Behandlung erfolgen soll(siehe DRY).
Komponenten – WS 2014/15 – Teil 8/Mocks 56
Einfache dyadische Operationen III
private void mockEpilog2(String[] expr) { try { InOrder inOrder= inOrder(stack); inOrder.verify(stack).push(expr[0]); inOrder.verify(stack).push(expr[1]); inOrder.verify(stack,times(2)).pop(); inOrder.verify(stack).push(anyString()); inOrder.verify(stack,times(1)).pop(); verifyZeroInteractions(stack); } catch (StackOverflowException | StackUnderflowException ex) {}}
Prüfungen
• Hier wird nun die exakte Reihenfolge der Aufrufe vonStack-Operationen geprüft.
• Daher geht der interne Algorithmus der zu testende Klasse in die Testfallspezifikation bzw. in die Simulation ein.
• Das wird dann Grey-Test genannt.
Komponenten – WS 2014/15 – Teil 8/Mocks 57
Erläuterungen
InOrder inOrder= inOrder(stack);inOrder.verify(stack).push(expr[0]);inOrder.verify(stack).push(expr[1]);
Über das inOrder-Objekt wird die Reihenfolge, nicht nur die bloßeExistenz eines Aufrufs geprüft.
verifyZeroInteractions(stack);
Damit wird geprüft, ob noch weitere Aufrufe statt gefundenhaben. Haben sie das, gibt es ein Test-Fail.
verify(stack).push(expr[0]);verify(stack).push(expr[1]);
Hier nur die bloße Existenz der aufgeführten Aufrufe geprüft.
Komponenten – WS 2014/15 – Teil 8/Mocks 58
Test auf die Division durch 0 I
@Test public void testDivisionBy0() { String expression[]= {"42","0","/"}; try { doNothing().when(stack).push("42"); doNothing().when(stack).push("0"); when(stack.pop()).thenReturn("0").thenReturn("42"); } catch (StackOverflowException | StackUnderflowException ex) {} try { int result= computer.compute(expression); fail("expected IllegalFormedExpressionException missed"); } catch (IllegalFormedExpressionException ex) {} try { InOrder inOrder = inOrder(stack); inOrder.verify(stack).push("42"); inOrder.verify(stack).push("0"); inOrder.verify(stack,times(2)).pop(); verifyZeroInteractions(stack); } catch (StackOverflowException | StackUnderflowException ex) {}}
Komponenten – WS 2014/15 – Teil 8/Mocks 59
Test auf die Division durch 0 II
• Das ist eine verkürzte Version der vorherigen Tests.
• Ohne Verkürzung gibt es ein Test-Fail bei verifyZeroInteractions().
Komponenten – WS 2014/15 – Teil 8/Mocks 60
Test auf falsche Syntax I
@Test public void testIllFormed3() { String expression[]= {"+","42","7"}; try { doThrow(new StackUnderflowException()).when(stack).pop(); } catch (StackUnderflowException ex) {} try { int result= computer.compute(expression); fail("expected IllegalFormedExpressionException missed"); } catch (IllegalFormedExpressionException ex) {} try { InOrder inOrder = inOrder(stack); inOrder.verify(stack).pop(); verifyZeroInteractions(stack); } catch (StackUnderflowException ex) {}}
Komponenten – WS 2014/15 – Teil 8/Mocks 61
Test auf falsche Syntax II
doThrow(new StackUnderflowException()).when(stack).pop();
Statt doNothing() kann auch mit doThrow() eine Exceptiongeworfen werden.
Komponenten – WS 2014/15 – Teil 8/Mocks 62
Vergleich Integrationstest – Mocks I
@Test public void testMultiplication() { String expression[]= {"3","4","*"}; mockProlog2(expression,"12"); try { int result= computer.compute(expression); assertEquals("3*4 -->",12,result); } catch (IllegalFormedExpressionException ex) { fail("unexpected exception occurred"); } mockEpilog2(expression);}
Version mit Mocks
Komponenten – WS 2014/15 – Teil 8/Mocks 63
Vergleich Integrationstest – Mocks II
@Test public void testMultiplication() { String expression[]= {"3","4","*"}; try { int result= computer.compute(expression); assertEquals("3*4 -->",12,result); } catch (IllegalFormedExpressionException ex) { fail("unexpected exception occurred"); }}
Version ohne Mocks
Viel kürzer!
Komponenten – WS 2014/15 – Teil 8/Mocks 64
Nachteile von Mocks
• Viel höherer Aufwand als mit Integrationstests.Im vorgestellten Beispiel hat die – zu testende Klasse 63 Zeilen
– simulierte Klasse 21 Zeilen,
– Simulationsklasse mit Tests (Mock) 180 Zeilen
– Testklasse ohne Mock 102 Zeilen
Und: es wurde noch nicht einmal alles Notwendige getestet.
• Selbst ausgereifte Bibliotheken wie Mockito führen nicht zu leicht lesbaren, eingängigen Tests.
• Die Pflege von Mocks ist bei Weiterentwicklungen genauso wichtig wie die der eigentlichen Klassen.
• Testklassen gehören zum eigentlichen Code und müssen dieselben Standards erfüllen.
Komponenten – WS 2014/15 – Teil 8/Mocks 65
Nach dieser Anstrengung etwas Entspannung...