Testen mit Mockito (Java)
Eine noch einfachere Methode um mal schnell in Java zu Testen, heißt Mockito. Diese sogenannten Mocks erlauben es, Testwerte in nur einer Zeile zu definieren!
Testfälle können selbst in kleinen Projekten ziemlich umfangreich werden! Wie sieht es dann erst in größeren Projekten aus? Eigentlich sollte man für jede Methode wenigstens einen Testfall schreiben! Was aber wenn abhängige Methoden, welche beispielsweise auf eine untere Schicht wie Datenbanken und Webservices zugreifen, noch nicht fertig gestellt sind?
… … [Stille] … Na, habe ich gerade einen Nerv getroffen? Macht sich spontane Resignation und Unlust breit? Richtet sich deine Kinnlade gen Fußboden? … Ich kann dich beruhigen, dagegen gibt es was! Stimmungsaufheller, zur Vorbereitung auf einen Tag mit Testfällen, waren gestern. Mockito heißt das Zauberwort. Nein ich Rede nicht von Alkohol, ich rede von viel besserem Stoff!
Um der zentralen und essentiellen Rolle von Testfällen nachzukommen und Abhängigkeiten zu entkoppeln, hat man sich früher mit sogenannten Stubs rumgeschlagen. Für jede Abhängigkeit wurde eine separate, eigens dafür angelegte, Klasse erstellt, welche von der noch unfertigen Klasse erbte. Dazu wurden die unfertigen Methoden überschrieben und fiktive, nahezu an den realen Betrag, rankommende Werte eingesetzt und zurückgegeben. Aufwändig, zeitintensiv und einfach nur ätzend. Mit Mockito kann ein Wert für eine Methode in einer Zeile gesetzt werden.
Ein kleines Mockito Beispiel: Step by Step
Wir nehmen an, dass wir eine Software schreiben, die einfache Währungsumrechnungen vornimmt. Wie in realen Projekten gibt es dazu mehrere Quellen. In meinem Fall eine Datenbank, in dieser liegt ein täglicher Kurs für die Konversion von Euro in eine andere Währung. Dann gibt es noch den Webservice von Yahoo, welche relativ aktuelle Kurse haben und für spezielle Währungen, beispielsweise den digitalen Bitcoin, beziehen wir stündlich eine Schnittstellendatei.
Keine Sorge wir kümmern uns nicht weiter um diese Datenquellen, dafür gibt es im Haus drei weitere Programmierer die sich um jeweils eine Quelle Kümmern. Wie ich feststellen durfte, handelt es sich dabei um drei extrem faule Programmierer. Nicht eine dieser Methoden ist fertig, dabei stellen diese die Grundlage für meine darauf aufgesetzten Routinen dar. Ärgerlich, was machen denn diese Bit-Schuppser den ganzen Tag? Redbull trinken? Witze über 1en und 0en? Die digitale Weltherrschaft an sich reißen?
Interface
Nun gut, zum Glück gibt es eine saubere Dokumentation und ich weiß, dass alle Klassen – welche die Umrechnungskurse holen – einer bestimmten Schnittstelle folgen. An dieser Stelle ein kleiner Schnittstellen-Exkurs. Eine Schnittstelle definiert eine Menge an Methoden. Verknüpft man später eine Klasse mit der Schnittstelle, so muss diese Klasse mindestens alle Methoden der Schnittstelle enthalten (wie man dann später sehen kann). In unserem Fall sieht die Schnittstelle wie folgt aus:
public interface ChangeRate {
/**
* gibt den Wechselkurs von Euro in SFR zurück
* @return
*/
public double getRate_EUROtoSFR();
/**
* gibt den Wechselkurs von Euro in Euro zurück
* @return
*/
public double getRate_EUROtoEURO();
/**
* gibt den Wechselkurs von Euro in Dollar zurück
* @return
*/
public double getRate_EUROtoDOLLAR();
/**
* gibt den Wechselkurs von Euro in Britische Pfund zurück
* @return
*/
public double getRate_EUROtoPOUND();
}
Entwurf aus der Entwicklung
Aus der Entwicklung gibt es auch schon einen ersten Entwurf, nur spiegelt dieser leider immer noch die träge Lustlosigkeit der Programmierer wieder. Dabei liste ich alle drei Klassen untereinander auf.
//From Database
public class ChangeRateFromDatabase implements ChangeRate {
/**
* Startet die Datenbankverbindung
* @param server
* @param name
* @param code
*/
public void startDatabaseConnection(String server, String name, String code)
{}
/**
* schließt die bestehende Datenbankverbindung
*/
public void closeDatabaseConnection()
{}
@Override
public double getRate_EUROtoSFR() {
//TODO get Rate from Database
}
@Override
public double getRate_EUROtoEURO() {
//TODO get Rate from Database
}
@Override
public double getRate_EUROtoDOLLAR() {
//TODO get Rate from Database
}
@Override
public double getRate_EUROtoPOUND() {
//TODO get Rate from Database
}
}
//From Webservice
public class ChangeRateFromYahoo implements ChangeRate {
@Override
public double getRate_EUROtoSFR() {
// TODO get Rate fom Yahoo-Webservice
}
@Override
public double getRate_EUROtoEURO() {
// TODO get Rate fom Yahoo-Webservice
}
@Override
public double getRate_EUROtoDOLLAR() {
// TODO get Rate fom Yahoo-Webservice
}
@Override
public double getRate_EUROtoPOUND() {
// TODO get Rate fom Yahoo-Webservice
}
}
//From Textfile
public class ChangeRateFromTxt implements ChangeRate {
/**
* öffnet Textfile mit Wechselkursen
* @param file
*/
public void openTxtFile(String file)
{}
@Override
public double getRate_EUROtoSFR() {
// TODO get Rate from txtfile
}
@Override
public double getRate_EUROtoEURO() {
// TODO get Rate from txtfile
}
@Override
public double getRate_EUROtoDOLLAR() {
// TODO get Rate from txtfile
}
@Override
public double getRate_EUROtoPOUND() {
// TODO get Rate from txtfile
}
}
Wie man hier sehr schön sehen kann, implementiert jede dieser Klasse das Interface „ChangeRate“. Damit wird sichergestellt, dass die vier Konversionsmethoden enhalten sind.
Meine Arbeit!
Jetzt kommt mein Teil der Programmierung. Ich setze auf den Klassen auf und programmiere eine darüber liegende Schicht. Das heißt, dass ich selber nicht auf die einzelnen Datenquellen zugreife sondern lediglich auf die Klassen die den Zugriff regeln. Das hat den Sinn, dass wenn ich an meiner Schicht etwas ändern möchte, muss ich lediglich meine Methoden ändern, jedoch nicht den Zugriff auf die Daten selber, da diese – auch wenn nicht umgesetzt – sauber gekapselt in sich sind.
public class MoneyChange {
/**
* beinhaltet die Quelle aus der die Wechselkurse
* gelesen werden
*/
private ChangeRate source;
public MoneyChange(ChangeRate source) {
this.source = source;
}
public double changeMoneyToDollar(double euro) {
if (euro >= 0) {
return euro * this.source.getRate_EUROtoDOLLAR();
} else {
return 0.0;
}
}
public double changeMoneyToSFR(double euro) {
if (euro >= 0) {
return euro * this.source.getRate_EUROtoSFR();
} else {
return 0.0;
}
}
public double changeMoneyToEuro(double euro) {
if (euro >= 0) {
return euro * this.source.getRate_EUROtoEURO();
} else {
return 0.0;
}
}
public double changeMoneyToPound(double euro) {
if (euro >= 0) {
return euro * this.source.getRate_EUROtoPOUND();
} else {
return 0.0;
}
}
}
Zugegeben, mein Programm ist etwas sehr einfach, da ich lediglich prüfe, ob es sich um ein gültigen Eurobetrag handelt und diesen dann mit dem Wechselkursfaktor multipliziere. Dennoch ist es ein Programm und im Gegensatz zu Faul, Fauler, am Fauelsten bin ich damit Fertig.
Austauschbarer Konstruktor
An dieser Stelle sollte ich noch meinen Konstruktor erklären, denn dieser legt immer die aktuelle Datenquelle fest. Diese Art hat sich in der Programmierung bewährt, selbst wenn es nur eine Datenquelle gibt bzw. nur eine Abhängigkeit. Warum? Weil man bedenken sollte, dass beispielsweise die Antwort von einem Webservice recht lang dauern kann. Schreibe ich jetzt 100 Testfälle, welche auf diesen Zugreifen, kann ein einfacher Testlauf schon sehr lang dauern. So habe ich die Möglichkeit zu Intervenieren und auf eine Fake- oder Testquelle auszuweichen.
Der Mockito Test
Und genau das mache ich jetzt auch. Da die drei realen Klassen noch keine brauchbaren Werte zurückliefern, erstelle ich einen Test mit angenommenen Werten über Mockito:
import static org.junit.Assert.*;
import org.junit.Test;
import org.mockito.Mockito;
public class Testing {
@Test
public void test() {
//Mockito erzeugen
ChangeRate cR = Mockito.mock(ChangeRate.class);
//Mockito Rückgaben bestimmen
Mockito.when(cR.getRate_EUROtoDOLLAR()).thenReturn(1.29);
Mockito.when(cR.getRate_EUROtoSFR()).thenReturn(1.25);
Mockito.when(cR.getRate_EUROtoEURO()).thenReturn(1.0);
Mockito.when(cR.getRate_EUROtoPOUND()).thenReturn(0.86);
//Mockito einsetzen
MoneyChange mc = new MoneyChange(cR);
double euro;
//Testfälle mit mc
euro = -2.0;
//ungültiger Euro Wert
assertEquals(0.0, mc.changeMoneyToDollar(euro), 0.01);
assertEquals(0.0, mc.changeMoneyToEuro(euro), 0.01);
assertEquals(0.0, mc.changeMoneyToPound(euro), 0.01);
assertEquals(0.0, mc.changeMoneyToSFR(euro), 0.01);
//wechseln
euro = 1;
assertEquals(1.29, mc.changeMoneyToDollar(euro), 0.01);
assertEquals(1.0, mc.changeMoneyToEuro(euro), 0.01);
assertEquals(0.86, mc.changeMoneyToPound(euro), 0.01);
assertEquals(1.25, mc.changeMoneyToSFR(euro), 0.01);
}
}
Wie du am Anfang der Klasse sehen kannst, erstelle ich ein Objekt des Interfaces. So findet jedes Objekt, welches damit implementiert wurde, darin Platz. Die folgende Schreibweise mag befremdlich wirken, jedoch ist sie äußerst praktisch. Denn ich kann jetzt sagen, dass wenn aus dem festgelegten Objekt eine Methode aufgerufen wird, wird ein bestimmter Wert zurück gegeben, ganz einfach. Jeder der schon einmal mit Stubs gearbeitet hat, wird dieses vorgehen zuschätzen wissen!
Entspanntes Testen mit Mockito!
Da jetzt meine Methoden im Objekt cR mit Testwerte überschrieben sind, kann ich diese an meine eigentliche Klasse übergeben. Hier kommt mein Konstruktor mit Quellenübergabe zum Einsatz. Denn jetzt setze ich einfach meine Fake-Mock-Test-Quelle ein und kann seelenruhig – ohne einen Gedanken an besagte und fragwürdige Programmierer zu verlieren – testen. Dazu benutze ich im übrigen JUnit 4. Im assertEquals steht der eigentliche Testfall. Als Hinweis möchte ich noch mit geben, dass es sich im Testfall bei der 0.01 um die Rundungstoleranz (Delta) handelt.
Download
- Download – Example: mocks example class
Neueste Kommentare