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 Software-Qualität Stephan Kleuker 211 Beispiel: DB-Mock (2/7): Erinnerung JDBC class DriverManager Connection con= Datenbankverbindung DriverManager.getConnection(...); herstellen Statement stmt= con.createStatement(); Datenbankanfrage Ergebnisse verarbeiten Verbindung zur DB schließen Software-Qualität 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; } Software-Qualität 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); } Software-Qualität 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" } }; Software-Qualität 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 } Software-Qualität }); 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")); } Software-Qualität 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 Software-Qualität Stephan Kleuker 218 Beispiel: Buchung, Konto, Logging (1/4) import org.mockito.Mockito; … @Before public void setUp() throws Exception { buchung = new Buchung(); } Direkte MockErstellung; Objekte direkt nutzbar, hier noch keine besonderen Rückgabewerte @Test public void testIstLiquide1(){ final Konto k = Mockito.mock(Konto.class); Buchung.logging = Mockito.mock(LogDatei.class); try { Prüfung, ob buchung.abbuchen(0, k, BETRAG1); Methoden so Assert.fail("fehlender Abbruch"); aufgerufen } catch (BuchungsException e) { Mockito.verify(k).istLiquide(BETRAG1); Mockito.verify(Buchung.logging).schreiben( "0 abgebrochen, insolvent"); Software-Qualität 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 { buchung.abbuchen(0, k, BETRAG1); } catch (BuchungsException e) { Assert.fail("nicht erwarteter Abbruch"); } //context.assertIsSatisfied(); Mockito.verify(k).istLiquide(BETRAG1); Mockito.verify(k).abbuchen(BETRAG1); Mockito.verify(Buchung.logging).schreiben("0 Spezifikation des Rückgabewerts (Default false) bearbeitet"); } Software-Qualität 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)) mehrere .thenReturn(true).thenReturn(false); Ergebnisse try { nacheinander buchung.abbuchen(0, k, BETRAG1); } catch (BuchungsException e) { Assert.fail("nicht erwarteter Abbruch"); geforderter mehrfacher } Methodenaufruf 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()); } Mockito-Matcher 221 Stephan Kleuker } Software-Qualität 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 Nutzung von Hamcrest.intThat(Matchers.greaterThan(42)))) Matchern .thenThrow(new NumberFormatException()); try { Werfen von buchung.abbuchen(0, k, 100); Exceptions Assert.fail("fehlender Abbruch"); } catch (NumberFormatException e) { Mockito.verify(k).istLiquide(Mockito .intThat(Matchers.greaterThan(42))); } catch (BuchungsException e) { Assert.fail("falsche Exception"); } } Software-Qualität 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(.) Software-Qualität 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) { Spy-Erstellung List<Integer> list = new LinkedList<Integer>(); list.add(1); alternative List<Integer> spy = Mockito.spy(list); Beschreibung für Ergebnis Mockito.doReturn(42).when(spy).get(0); System.out.println(spy.get(0) +" :: " + spy.size()); @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()); } Software-Qualität 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) Software-Qualität 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 Software-Qualität 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 Software-Qualität Stephan Kleuker 227