Beispiel: DB-Mock (1/7) Aufgabe: DB, auf die vereinfachend nur lesend zugeriffen wird mocken warum: benötigte keine DB-Lizenz, garantiert gleiche Werte ohne aufwändiges reset, kein Zeitverlust durch Verbindungsaufbau Ansatz: Statement-Interface von JDBC mocken Mock in eigene Klasse zur Wiederverwendung auslagern Hinweis: Zur Vereinfachung des Beispiels wird SQL nicht ganz sauber eingesetzt Stephan Kleuker 211
Beispiel: DB-Mock (2/7): Erinnerung JDBC Datenbankverbindung herstellen class DriverManager Connection con= DriverManager.getConnection(...); Statement stmt= con.createstatement(); Datenbankanfrage Ergebnisse verarbeiten Verbindung zur DB schließen ResultSet rs = stmt.executequery(...); rs.next(); int n = rs.getint("knr"); con.close(); Stephan Kleuker 212
Beispiel: DB-Mock (3/7): Programm (1/2) Hinweis: Programm auf Testbarkeit ausgelegt; einfaches Einfügen eines Statement-Objekts package noten; import java.sql.resultset; import java.sql.sqlexception; import java.sql.statement; public class Statistik { private Statement statement; public Statistik(){ public void setstatement(statement statement){ this.statement=statement; Stephan Kleuker 213
Beispiel: DB-Mock (4/7): Programm (2/2) public double studidurchschnitt(string name) throws SQLException{ int anzahl=0; int summe=0; ResultSet rs= statement.executequery( "SELECT * FROM Noten WHERE Studi ='" + name + "'"); while (rs.next()) { anzahl++; summe += rs.getint(3); if (anzahl == 0) return 0d; else return summe/ (anzahl * 100d); Stephan Kleuker 214
Beispiel: DB-Mock (5/7): Mock-Aufbau (1/2) package noten; import java.sql.resultset; import java.sql.statement; import org.jmock.expectations; import org.jmock.mockery; public class DBMock { public Statement dberstellen() throws Exception { final Mockery context = new Mockery(); final Statement st = context.mock(statement.class); final String[][] pruefungen = { // Beispieltabelle { "Ute", "Prog1", "400", { "Uwe", "Prog1", "230", { "Ute", "Prog2", "170" ; Stephan Kleuker 215
Beispiel: DB-Mock (6/7): Mock-Aufbau (2/2) context.checking(new Expectations() { { final ResultSet r = context.mock(resultset.class,"r"); context.checking(new Expectations() { { oneof(r).next(); will(returnvalue(true)); oneof(r).next(); will(returnvalue(true)); oneof(r).next(); will(returnvalue(false)); oneof(r).getint(3); will(returnvalue(integer.parseint(pruefungen[0][2]))); oneof(r).getint(3); will(returnvalue(integer.parseint(pruefungen[2][2]))); ); allowing(st).executequery( "SELECT * FROM Noten WHERE Studi='Ute'"); ); will(returnvalue(r)); Stephan Kleuker 216
Beispiel: DB-Mock (7/7): Tests (Ausschnitt) public class StatistikTest { private Statement db; private Statistik s; @Before public void setup() throws Exception{ db = new DBMock().dbErstellen(); s = new Statistik(); s.setstatement(db); @Test public void testschnittute() throws SQLException { Assert.assertTrue(2.85 == s.studidurchschnitt("ute")); Stephan Kleuker 217
Alternativwerkzeug Mockito generell: immer wieder nach Alternativen suchen Mockito basierte auf EasyMock (auch Alternative), hat dann eigenen Weg eingeschlagen (https://code.google.com/p/mockito/) Vor Werkzeugvergleich immer Kriterien überlegen, z. B. wirklich benötigter Funktionsumfang unterstützte Technologien Lizenz, Kosten Größe des Entwicklungsteams Dokumentation, Support Kriterien individuell im Projekt gewichten Stephan Kleuker 218
Beispiel: Buchung, Konto, Logging (1/4) import org.mockito.mockito; @Before public void setup() throws Exception { buchung = new Buchung(); @Test public void testistliquide1(){ final Konto k = Mockito.mock(Konto.class); Buchung.logging = Mockito.mock(LogDatei.class); try { buchung.abbuchen(0, k, BETRAG1); Assert.fail("fehlender Abbruch"); catch (BuchungsException e) { Mockito.verify(k).istLiquide(BETRAG1); Mockito.verify(Buchung.logging).schreiben( Direkte Mock- Erstellung; Objekte direkt nutzbar, hier noch keine besonderen Rückgabewerte Prüfung, ob Methoden so aufgerufen "0 abgebrochen, insolvent"); Stephan Kleuker 219
Beispiel: Buchung, Konto, Logging (2/4) @Test public void testistliquide3(){ Konto k = Mockito.mock(Konto.class); Buchung.logging = Mockito.mock(LogDatei.class); Mockito.when(k.istLiquide(BETRAG1)).thenReturn(true); try { Spezifikation des buchung.abbuchen(0, k, BETRAG1); Rückgabewerts catch (BuchungsException e) { (Default false) Assert.fail("nicht erwarteter Abbruch"); //context.assertissatisfied(); Mockito.verify(k).istLiquide(BETRAG1); Mockito.verify(k).abbuchen(BETRAG1); Mockito.verify(Buchung.logging).schreiben("0 bearbeitet"); Stephan Kleuker 220
Beispiel: Buchung, Konto, Logging (3/4) @Test public void testistliquide4(){ final Konto k = Mockito.mock(Konto.class); Buchung.logging = Mockito.mock(LogDatei.class); Mockito.when((k).istLiquide(BETRAG1)).thenReturn(true).thenReturn(false); try { buchung.abbuchen(0, k, BETRAG1); catch (BuchungsException e) { Assert.fail("nicht erwarteter Abbruch"); try { buchung.abbuchen(0, k, BETRAG1); Assert.fail("fehlender Abbruch"); catch (BuchungsException e) { Mockito.verify(k,Mockito.times(2)).istLiquide(BETRAG1); Mockito.verify(k).abbuchen(org.mockito.Matchers.anyInt()); Mockito.verify(Buchung.logging,Mockito.times(2)).schreiben(Mockito.anyString()); mehrere Ergebnisse nacheinander geforderter mehrfacher Methodenaufruf Mockito-Matcher Stephan Kleuker 221
Beispiel: Buchung, Konto, Logging (4/4) import org.hamcrest.matchers; @Test public void testistliquide5(){ Konto k = Mockito.mock(Konto.class); Buchung.logging = Mockito.mock(LogDatei.class); Mockito.when(k.istLiquide(Mockito.intThat(Matchers.greaterThan(42)))).thenThrow(new NumberFormatException()); try { buchung.abbuchen(0, k, 100); Assert.fail("fehlender Abbruch"); catch (NumberFormatException e) { Mockito.verify(k).istLiquide(Mockito.intThat(Matchers.greaterThan(42))); catch (BuchungsException e) { Assert.fail("falsche Exception"); Nutzung von Hamcrest- Matchern Werfen von Exceptions Stephan Kleuker 222
Kleine Verwirrung Mockito hat eigene Matcher-Klasse org.mockito.matchers Mockito.verify(k).abbuchen(org.mockito.Matchers.anyInt()); Hinweis: wenn ein Argument durch Matcher ersetzt, müssen alle ersetzt werden verify(mock).somemethod(anyint(), anystring(), eq("third argument")); Mockito-Matcher ermöglichen auch die Nutzung von Hamrest-Matchern Mockito.verify(k).istLiquide(Mockito.intThat(org.hamcrest.Matchers.greaterThan(42))); Methoden : argthat(.), booleanthat(.), bytethat(.), charthat(.), doublethat(.), floatthat(.), intthat(.), longthat(.), shortthat(.) Stephan Kleuker 223
Spying Möglichkeit Methoden realer Objekte zu verändern sinnvoll bei Legacy-Code oder um Zugriffe auf externe Systeme zu vermeiden public static void main(string[] args) { List<Integer> list = new LinkedList<Integer>(); list.add(1); List<Integer> spy = Mockito.spy(list); Mockito.doReturn(42).when(spy).get(0); System.out.println(spy.get(0) +" :: " + spy.size()); Spy-Erstellung alternative Beschreibung für Ergebnis @SuppressWarnings("unchecked") List<Integer> list2 = Mockito.mock(List.class); 42 :: 1 list2.add(1); 42 :: 0 Mockito.doReturn(42).when(list2).get(0); System.out.println(list2.get(0) +" :: " + list2.size()); Stephan Kleuker 224
Nicht alles kann gemockt werden (JMock, Mockito) public static void main(string[] args) { String s = Mockito.mock(String.class); Mockito.doReturn(42).when(s).length(); System.out.println(s+ " hat Laenge" + s.length()); Exception in thread "main" org.mockito.exceptions.base.mockitoexception: Cannot mock/spy class java.lang.string Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types at MockitoSpielerei.main(MockitoSpielerei.java:8) Stephan Kleuker 225
etwas Spielerei public static void main(string[] args) { BigInteger s = Mockito.mock(BigInteger.class); Mockito.doReturn("42").when(s).toString(); System.out.println("s hat Wert " + s +" :: "+s.longvalue()); BigInteger tt = new BigInteger("1234567890123456789"); BigInteger t = Mockito.spy(tt); Mockito.doReturn("42").when(t).toString(); System.out.println("t hat Wert " + t +" :: "+t.longvalue()); System.out.println(t.getClass()); s hat Wert 42 :: 0 t hat Wert 42 :: 1234567890123456789 class $java.math.biginteger$$enhancerbymockitowithcglib$$8e32a13f Stephan Kleuker 226
weitere Möglichkeiten / Fazit Mockito bietet einige weitere Möglichkeiten, dabei immer teilweise spezielle Randbedingungen beachten http://docs.mockito.googlecode.com/hg/latest/org/mockito/ Mockito.html Mockito etwas einfacher zu nutzen als JMock; insbesondere mehr "normales Java" Mockito und JMock haben kleine Anteile, die der andere nicht kann (man kann beide zusammen einsetzten) generell kritischer Werkzeugvergleich immer sinnvoll; oft auch gemeinsame Nutzung eine Lösung Stephan Kleuker 227