Ja, die Diskussion lief schon an anderen Stellen. Bisher konnte mir noch niemand sagen, was an einem
@Test
public void testFoo()
{
int result = MyStaticMethods.compute(1.2,3,4,Arrays.asList("Bar", "Baz"));
assertEquals(42, result);
}
nun „schlecht“ oder „falsch“ oder „untestbar“ sein soll, und die Argumentationsschiene mit dem Mocking entzieht sich für mich jeglicher Grundlage, wenn nichts zu mockendes DA ist. (Es geht ja NICHT um Zustände, oder Datenbankzugriffe mit Hartverdrahteten login-Informationen…). Eine Funktion erfüllt eine bestimmte Funktion. Rein funktion-al. Eine Eingabe wird eindeutig auf eine Ausgabe gemappt. Wenn jemand eine zu testende Business-Methode hat wie
int computeMax(int x, int y, int z)
{
return Math.max(x,Math.max(y,z));
}
ist die Tatsache, dass Math#max nicht mockbar ist, ja auch kein Problem.
[quote=Marco13]Bisher konnte mir noch niemand sagen, was an einem @Test public void testFoo() { int result = MyStaticMethods.compute(1.2,3,4,Arrays.asList("Bar", "Baz")); assertEquals(42, result); }nun “schlecht” oder “falsch” oder “untestbar” sein soll,[/quote]
Weil es schlicht nicht um den Test der static-Methode selbst, soondern deren Abhängigkeit geht.
[quote=Marco13;131396]Wenn jemand eine zu testende Business-Methode hat wieint computeMax(int x, int y, int z) { return Math.max(x,Math.max(y,z)); }ist die Tatsache, dass Math#max nicht mockbar ist, ja auch kein Problem.[/quote]Eigentlich doch.
Wenn mal jemand bei Oracle auf die Idee kommen sollte, da was in Math#max was kaputt zu machen fallen Deine Tests fälschlicher Weise auf die Nase.
Wenn die Wahrscheinlichkeit dafür hinreichend gering ist (wie bei Math#max) kann man das ignorieren.
Je instabiler aber diese Abhängigkeit ist, je wichtiger ist es, sie wegmocken zu können.
An dem Test ansich ist überhaupt nichts verkehrt. Aber der direkte Aufruf statischer Methoden erschwert potenziell Tests von Units, die das machen. Denn hier musst Du Dir Testdaten überlegen, die die statische Methode adäquate Ergebnisse zurück liefern lassen, die für verschiedene Testfälle beim Test der abhängigen Unit benötigt werden (im Gegensatz zum Mock, das Dir die für den Testfall benötigten Daten einfach liefert).
Das ist für mich aber auch nicht unbedingt ein Argument gegen statische Methoden. „Schuld“ hätte hier allenfalls derjenige, der die direkte Abhängigkeit zur statischen Methode in seine Unit reinprogrammiert hat. Mit funktionalen Interfaces und Methodenreferenzen gibt es nun ja eine sehr einfache Möglichkeit, die Abhängigkeit „wegzukapseln“. Das ist in vielen Fällen sinnvoll, in vielen anderen Fällen -bspw. bei (package-)privaten Hilfsmethoden- aber auch over engeneered.
[quote=Timothy_Truckle]Wenn mal jemand bei Oracle auf die Idee kommen sollte, da was in Math#max was kaputt zu machen fallen Deine Tests fälschlicher Weise auf die Nase.
Wenn die Wahrscheinlichkeit dafür hinreichend gering ist (wie bei Math#max) kann man das ignorieren.
Je instabiler aber diese Abhängigkeit ist, je wichtiger ist es, sie wegmocken zu können.[/quote]
das heißt konkrent also, wenn die zu testende Methode X max(a,b)/2 errechnet,
dann könnte man extra ein Mock-Objekt dazubauen, welches für max() den richtigen Wert zurückliefert,
ganz egal welche Parameter man an die Methode X übergibt, oder höchstens noch geprüft ob sie von der Methode X richtig weitergeleitet werden,
und man testet nur was die Methode sonst selber macht, ob sie /2 korrekt rechnet?
das klingt ja reichlich unrealistisch und auch nicht wirklich als eine Aufgabe unter
aufgeführt, glaube ich
ist das irgendwo real im Einsatz, an ausführlichen Beispielen praktisch anzuschauen?
sind nicht einfache statische Methoden wie Math.max() programmweit überall im Einsatz, wie will man das machen,
müsste jede Klasse auf (viele) solche Methoden in Grundklasse oder in einem zugeordneten Hilfsobjekt zugreifen können?
und wären dann nicht bei vielen Tests immer wieder solche kleinen Methoden einzeln zu mocken?..
widerspricht das nicht eigentlich auch
?
gilt strenggenommen für Mocken hinsichtlich DB, Login usw. auch, aber da sei mal erlaubt,
einen gewissen Programmhintergrund korrekt aufzubauen + große aufwendige Programmabschnitte abzukürzen…
aber so einfache Rechnungen, sollte man da nicht lieber die Implementierung ignorieren,
was wenn statt max() was anderes verwendet wird, darf man dann nicht vergessen auch im Test was anderes als max() zu mocken?..
wäre es nicht besser hier gleich max() & Co. (ob intern oder statisch) quasi mitzutesten?
äh, wo kann man denn dann noch statische Methoden gebrauchen wenn Tests doch sicher idealerweise alles abdenken sollten,
und in allem Abgedeckten statische Methoden nicht rein kommen sollen?
kann man statische oder sonstige Methoden noch anders als per ‚direkte Abhängigkeit‘ verwenden?
Mist, hatte auf der Arbeit schon eine recht ausführliche Antwort geschrieben, wurde dann aber unterbrochen, und hab’s nicht abgeschickt.
Man muss sich auf bestimmte Dinge verlassen können. Die Argumentation finde ich in diesem Sinne schon SEHR weit hergeholt. Was wäre, wenn Collections.sort auf einmal nicht mehr richtig sortiert? Oder um die Argumentation aufs asbsurdum zu reduzieren: Was macht dich denn so sicher, dass JUnits (statische Methode ;-)) assertEquals richtig implementiert ist? Nochmal, bestimmte Funktionen sind „atomare“ Building Blocks, auf die man sich verläßt, und DASS man sich darauf verlassen kann, wird sichergestellt, indem die Funktion selbst geUnitTestet ist. Eine statische Methode ist prinzipbedingt frei von Zuständen und frei von Abhängigkeiten. Der Rückgabewert hängt NUR von der Eingabe ab. Ich wüßte nicht, was es da zu Mocken geben sollte. (Nochmal: Dass sowas wie eine DatabaseConnection oder eine FileList etwas anderes ist, sollte klar sein). Wenn man meint, sich auf nichts verlassen zu können außer ±*/=, dann dürften Tests seeehr aufwändig werden…
[quote=Marco13]Man muss sich auf bestimmte Dinge verlassen können.[/quote]Womöglich hast Du das übersehen:[quote=Timothy_Truckle;131407]Wenn die Wahrscheinlichkeit dafür hinreichend gering ist (wie bei Math#max) kann man das ignorieren.
Je instabiler aber diese Abhängigkeit ist, je wichtiger ist es, sie wegmocken zu können.[/quote]
bye
TT
[quote=SlaterB;131413]das heißt konkrent also, wenn die zu testende Methode X max(a,b)/2 errechnet,
dann könnte man extra ein Mock-Objekt dazubauen, welches für max() den richtigen Wert zurückliefert,
ganz egal welche Parameter man an die Methode X übergibt, oder höchstens noch geprüft ob sie von der Methode X richtig weitergeleitet werden,
und man testet nur was die Methode sonst selber macht, ob sie /2 korrekt rechnet?[/quote]Genau dass, wobei das Beispiel natürlich zu trivial für das reale Leben ist…
[quote=SlaterB;131413]ist das irgendwo real im Einsatz[/quote]Ja, bei meinen Projekten.
[quote=SlaterB;131413]an ausführlichen Beispielen praktisch anzuschauen?[/quote]Die Projekte sind leider nicht öffendlich aber wenn’s Dich interessiert kann ich Dir das mal in einer Webex-Session (kann FF-Hello auch Screensharing?) zeigen, darfst halt nichts über den fachlichen Kontext weitererzählen…
[quote=SlaterB;131413]aber so einfache Rechnungen, sollte man da nicht lieber die Implementierung ignorieren,[/quote]Ich weiss, das Du das jetzt anders meinst, aber genau dass will ich ja im Test, die Implementierung solcher Hilfsrechnungen ignorieren.
[quote=SlaterB;131413]wäre es nicht besser hier gleich max() & Co. (ob intern oder statisch) quasi mitzutesten?[/quote]nein, aus 2 Gründen:
Wenn sich irgenwas an der (potenziell) statischen Methode ändert hat das Einfluss auf alle Test, die diese Methode nutzen. Wenn beispielsweise der erlaubte Bereich für die Eingabewerte verringert wird (was ja aus fachlichen Gründen durchaus mal sein kann), bekommen jetzt alle Test, die die Methode “mittesten” eine Exception und müssen angepasst werden. Das ist der Verstoß gegen das “M” in RTFM.
[QUOTE=Timothy_Truckle;131421]Womöglich hast Du das übersehen:
[/QUOTE]
Nein, aber … „Wahrscheinlichkeiten“ sind so eine Sache. Die Wahrscheinlichkeit dafür, dass Collections.sort sich ändert, könnte man als „nahe 0“ ansehen. Aber zwischen Java 6 und Java 7 wurde das interne Sortierverfahren auf TimSort umgestellt. Warum hat das niemand gemerkt, und warum mußte man da nichts weg-mocken? Ganz einfach: Collections.sort ist eine statische Funktion, mit („bis zum Anschlag“) mathematisch ausformulierbaren Vor- und Nachbedingungen - und DIE haben sich natürlich nicht geändert. Es ist ein Building Block, der innen Schwarz ist.
Ich würde deswegen für ~„die Notwendigkeit, zu mocken“ lieber ein fachliches, objektives Kriterium heranziehen. Und aus meiner (wie immer eingeschränkt-subjektiven) Sicht ist ein Ansatz eben: Eine statische Funktion (die anderweitig getestet wird) ist ein Building-Block, der nicht gemockt werden muss. (Was dann DOCH gemockt werden muss, ist damit nicht beantwortet, aber das ist ja auch ein anderer Punkt).
obwohl diese verwendenden Methoden real wahrscheinlich nicht mehr funktionieren, jedenfalls auch mit den Testdaten als nicht funktionierend zu erkennen sind, sollen diese Tests weiter erfolgreich sein (indem die fehlerhafte Komponente durch Dummy gemockt ist)?
allein darauf verlassen dass die statische Methode oder sonst wie (vermeidbar) gemockte Komponente für sich ordentlich getestet ist und dort dann den Fehler erkennen?
hat gewiss den Vorteil dass man schneller die tatsächlich falschen Stellen erkennt, falls alles bestens abgedeckt ist,
aber mit der Gefahr das Problem evtl. ganz zu übersehen, falls der eine entscheidende Test nicht ganz vollständig ist (oder wie für API gar nicht im eigenen Testlauf dabei…)
lieber doch viele Tests fehlschlagend mit nicht direkt klarer Aussage und dann evtl. etwas aufwendiger untersuchen, was darin schief läuft,
dafür Fehler-Finden 10fach abgesichert?
[quote=SlaterB]obwohl diese verwendenden Methoden real wahrscheinlich nicht mehr funktionieren, jedenfalls auch mit den Testdaten als nicht funktionierend zu erkennen sind, sollen diese Tests weiter erfolgreich sein?[/quote]Ja, weil die fachliche Änderung eben nicht in den Units steckt, die geänderte Unit verwenden müssen deren Test auch nicht angepasst werden.
Das Die mit (Checked) Exceptions umgehen können müssen, die von der “Utility-Methode” explizit geworfen werden ist ein eigener Testfall für die “abhängigen” Units und selbst dieser sollte sich nicht ändern.
Übrigens ist das Testen der Fehlerfälle noch ein Argument gegen nicht mock-bare Abhängigkeiten. Ich muss mir beim Test nicht einen abbrechen, die Eingabewerte so zu wählen, dass (die richtige von mehreren) Methode eine Exception wirft sondern schreibe einfach:when(mockDependency.formerStaticMethod()).thenThrow(new MyCheckedOrUncheckedException("expect this text in any result");
[quote=SlaterB;131425]lieber doch viele Tests fehlschlagend mit nicht direkt klarer Aussage und dann evtl. etwas aufwendiger untersuchen, was darin schief läuft,
dafür Fehler-Finden 10fach abgesichert? [/quote]nein.
Wenn ich dem/den Namen des/der fehlgeschlagenden Tests schon entnehmen kann, welche Anforderung nicht erfüllt ist spare ich genau diese Analyse-Zeit.
und du hast in deinem Code (sehen muss ich den nicht, danke, kein Risiko auf Verteilung) für jedes Collections.sort()
oder ähnliches einen fertigen Mock der die Liste auf anderem Wege genau die richtige Reihenfolge bringt?
höchst erstaunlich, aber bitte
in meinem Beispiel war noch /2 zu rechnen, das ist nun aber schon ok und nicht noch durch eine mockbare Methode divide() zu ersetzen, die in der Mock-Version auch bereits das richtige Ergebnis liefert?
ok, wird polemisch
ohne konkrete Beispiele schlecht zu besprechen,
Collections.sort() oder irgendwas anderes sortierendes aus der Java-API (oder doch gewiss eine andere Standard-API) ist aber schon etwas höher,
überhaupt alles wie Listen, oder Java8-Stream mit flatMap/ filter usw.,
vieles davon ist zwar nicht statisch, aber doch genauso fertige Methoden aufgerufen, die doch wohl kaum gemockt werden, auf die API verlassen, also im Grunde selbe Frage?
Math.max() ist dann sicher auch weit auf der grünen Seite…
(schriebst du ja auch schon)
*** Edit ***
ein Beispiel-Versuch noch, ganz ohne static, nur zur Frage nach diesem ominösen Mocken vieler Methoden:
ohne Betrachtung der Sinnhaftigkeit ein Beispiel mit einer Methode, die zu einem netto-Preis den Brutto berechnet,
keine weiteren Infos wie Artikel-Art dazu, nur netto-Preis,
vielleicht aber kompliziert unterschiedlicher Zinssatz je nach Höhe,
könnte fast statisch sein, ist hier bereits nicht-statisch, aber jedenfalls für sich ziemlich wichtig, umfassend mit Test ausgestattet
zweite Methode summiereBrutto() berechnet Brutto von mehreren Posten im Einkaufskorb mit jeweils Netto-Preis,
legen wir mal fest für jeden Artikel einzeln Brutto zu berechnen,
damit man weiß was als als Endergebnis herauskommen soll muss man in der Tat evtl. nebenbei alles selber nachrechnen,
aber ist es nun wirklich realistisch, die Methode berechneBrutto() zu mocken, darin eine komplizierte Fallunterscheidung,
für jeden der z.B. 5 übergebenen Netto-Preise den zugehörigen Brutto manuell zurückgeben??
kann man nicht einfach summiereBrutto() durchrechnen lassen wie es ist und nur das Endergebnis vergleichen?
diese Art des Mockens ist mir ganz neu, scheint ja extrem aufwendig und reichlich unnötig,
wie gesagt im Wiki zum Mocken nicht direkt erwähnt und meine übliche Tirade: noch nie irgendwo gelesen/ gesehen, gibt es Links dazu?
freilich generell schwer, fertige Unit Test-Beispiele zu finden
die verschiedenen Mock Frameworks haben doch fertige Beispiele. Das hier von jMockit geht doch etwa in die Richtung die SlaterB gerade sehen will?
Die zu testende Klasse:
{
private final Queue<InputStream> sequentialInputs;
private InputStream currentInput;
public ConcatenatingInputStream(InputStream... sequentialInputs)
{
this.sequentialInputs = new LinkedList<InputStream>(Arrays.asList(sequentialInputs));
currentInput = this.sequentialInputs.poll();
}
@Override
public int read() throws IOException
{
if (currentInput == null) return -1;
int nextByte = currentInput.read();
if (nextByte >= 0) {
return nextByte;
}
currentInput = sequentialInputs.poll();
return read();
}
}```
Der Test:
```@Test
public void concatenateInputStreams(
@Injectable final InputStream input1, @Injectable final InputStream input2)
throws Exception
{
new Expectations() {{
input1.read(); returns(1, 2, -1);
input2.read(); returns(3, -1);
}};
InputStream concatenatedInput = new ConcatenatingInputStream(input1, input2);
byte[] buf = new byte[3];
concatenatedInput.read(buf);
assertArrayEquals(new byte[] {1, 2, 3}, buf);
}```
Von: [JMockit - Tutorial - Mocking](http://jmockit.org/tutorial/Mocking.html)
Gruß
Fancy
[ot]
> Note: Your post is undergoing an additional security check by an administrator and / or moderator. It might appear delayed by a few minutes.
wtf?[/ot]
ist das nicht eher so wie Test-Daten-Array [1,2] + [3] an irgendwas zu übergeben was dann erfolgreich [1, 2, 3] liest?
entsprechende ByteArrayInputStreams hätten es hier vielleicht auch getan, aber vielleicht so schöner
ach steht ja auch dort
This class could easily be tested without mocking by using ByteArrayInputStream objects for input, but lets say we want to make sure that the InputStream#read() method is properly invoked on each input stream passed in the constructor. The following test will achieve this.
alles dort ist reichlich nach Umsetzungsbeispielen, zeigt dass es technisch möglich ist, ok,
und es gibt ja auch nachvollziehbare Fälle wie unten Mock für Login-Methode, da ist es wichtig,
klassischer Mock-Fall wie bekannt
aber bei simplen (eben insbesondere static-möglichen, ressourcenfreien) Logik-Berechnungen?
nur um anderen Code auszuschließen damit evtl. Fehler dort nicht durchschlägt?
[quote=SlaterB]und du hast in deinem Code (sehen muss ich den nicht, danke, kein Risiko auf Verteilung) für jedes Collections.sort()
oder ähnliches einen fertigen Mock der die Liste auf anderem Wege genau die richtige Reihenfolge bringt?
höchst erstaunlich, aber bitte[/quote]Nein.
Das habe ich nicht behauptet.
Meine Aussage war: Wenn die Chance besteht, dass die Abhängigkeit sich ändern könnte und das Abhängigkeiten zu 3rd party Bibliotheken als “Stabil” angesehen werden können.
Außerdem war der Ausgangspunkt ob man selbst static Methoden schreiben sollte oder nicht. Da mit solchen “System”-Klassen zu argumentieren passt da irgendwie nicht.
[quote=SlaterB;131431]kann man nicht einfach summiereBrutto() durchrechnen lassen wie es ist und nur das Endergebnis vergleichen?[/quote]Der Punkt ist: wir reden nicht über Tests von Klassen, die dieses Interface implementieren, sondern von Klassen, die Objekte benutzen, die dieses Interface implementieren:```@Test druckeRechnung_EinkaufskorbWert4711_rendertGesamtSumme(){
Rechner rechner = mock(Rechner.class);
Einkaufskorb einkaufskorb = mock(Einkaufskorb.class);
// ohne das mock müßtest Du hier einen kompletten Warenkorb erzeugen, der den gewünschten Wert hat, was den Konfigurationsaufwand hier erheblich erhöht.
// [edit] und die Implementierungen für Rechner und Einkaufskorb müssen noch nicht existieren, damit der Test funktioniert![/edit]
when(rechner.summiereBrutto(any(Einkaufskorb.class)).thenReturn(BigDecimal.valueOf(47.11));
RechnungsDrucker rd = new RechnungsDrucker(rechner, einkaufskorb);
String rechnungHtml = rd.print();
verify(rechner).summiereBrutto(einkaufskorb);
assertThat("Summe in Rechnung", rechnungHtml, containsString("<tr class='overall'><td class='label'>GesamtSumme:</td><td class='currency'>47,11€</td></tr>"));
}```
[quote=SlaterB;131431]aber ist es nun wirklich realistisch, die Methode berechneBrutto() zu mocken, darin eine komplizierte Fallunterscheidung,
für jeden der z.B. 5 übergebenen Netto-Preise den zugehörigen Brutto manuell zurückgeben??[/quote]Das musst Du beim Test der Implementierung des Interfaces sowieso machen, und mit TDD macht man das ganz automatisch.
[quote=SlaterB;131431]kann man nicht einfach summiereBrutto() durchrechnen lassen wie es ist und nur das Endergebnis vergleichen?[/quote]Womit vergleichen?
Also die Frage ist: wie stellst Du fest, das das Ergebnis richtig ist?
Genau: indem Du Eingabe und erwartete Ausgabe explizit als Literale vor gibst.
Die selbe Rechnung im Test und im Produktivcode zu machen ist schlicht dumm. Wenn beide Rechnungen den gleichen Fehler haben (weil per Copy/Paste entstanden) ist der Test wertlos.
[quote=SlaterB;131431]diese Art des Mockens ist mir ganz neu, scheint ja extrem aufwendig und reichlich unnötig,[/quote]weder noch.
Ich denke der zentrale Knackpukt ist: ich teste Funktionalität der Unit. Dabei ist eben alle Funktionalität, die nicht von der Unit selbst, sondern von anderen Units, die sie verwendet, erbracht wird nicht im Scope des Tests, weshalb diese anderen Units gemockt werden.
Im vorletzten Eclipse-Magazin (2/16) gab’s da 'nen Arikel dazu.
Ein Blick in das git-repo dazu könne womöglich auch einiges klären
für Test Rechnungsdrucker sich den hier unwichtigen internen Aufbau des Einkaufskorbs einzusparen,
nur die gewünschte Endsumme vorzugeben,
ist schon bisschen was anderes als der angedachten Test der Summierung des Einkaufskorbs selber…,
was wäre denn mit Test der Summierung des Einkaufskorbes, Brutto-Rechner extern anderes Modul?
Eingabe in den Einkaufskorb Netto Preise 12 Euro, 7 Euro, 20 Euro,
man weiß dass der Brutto dazu 15.03, 7.77, 24.31 ist, manuell nachgerechnet, Summe 47.11
aber muss man nun diese Einzelberechnung auch mocken, diese drei Zwischenwerte explizit vorgeben?
reicht doch auch wenn das Programm das selber aus 12 + 7 + 20 errechnet, nur die korrekte Summe abgefragt
wenn sich der Standard-Zinssatz oder irgendwas anderes programmweit ändert,
dann rechnet der gemockte Test weiter stoisch seine korrekte Summe 47.11 zu Eingaben 12 + 7 + 20 aus,
weil ja nur die auch schon fertig vorgegebenen Zwischenergebnisse addiert werden,
witzlos, die mögliche Mitüberprüfung der Rechnung wird zum Check simpler Addition degradiert
dagegen würde intern normal berechnete abweichende Summe 49.45 einen Fehler im Test gegen,
man könnte nun untersuchen ob das manuell neu gerechnet stimmt und dann 49.45 als neues Endergebnis setzen,
Test wieder korrekt, hat bestens mitgearbeitet
beim Eclipse-Beispiel weiß ich nicht recht,
da gibt es Tests wie
über FeldGeometrie, intern komplexen ZellenIterator, ZelleMitNachbarn,
alle Klassen mit deren normalen Methoden verwendet, nichts zu mocken nötig,
wohl als Einheit angesehen die gemeinsam zu testen ist
korrekte vollständige Vorgabe der Karte wie etwa feldGeometrie.setzeKarte("123 456 789 ");
erlaubt komplexe Prüfungen wie korrekte Nachbarn eines bestimmten Feldes,
und dann
zum Test von Spiel,
dort keine Lust das interne FeldGeometrie simpel passend in einer Zeile zu definieren,
stattdessen mega kompliziert vorhergesehen, welche Methoden benötigt werden und diese das richtige zurückgeben lassen?
ein paar inhaltliche Mocks wie Rückgabe der Nachbar-Liste sind noch halbwegs logisch,
aber die letzten beiden haben gar keine Funktion, Rückgabe egal, nur Dummy-mäßig ergänzen damit der Ablauf nicht an dieser Stelle streikt…,
und andere wie in meinen Code Zeile 8 (+ 9) ist interne nötige Zwischenfunktionalität explizit nachgebaut, gruselig
ermöglicht sicher genau was gewünscht, Spiel getestet ohne ein feldGeometrie konkrekt anzulegen,
als theoretisches Beispiel in jedem Fall interessant,
aber ob es nicht besser wäre, einfach feldGeometrie sauber anzulegen was hier nichts kostet?
hmm…
Nein, das ist genau der Witz. Man schmeißt alle externen Abhängigkeiten raus bis das nur noch das übrigbleibt was wirklich in der zu testenden Klasse passiert. Und wenn das eben nur eine Addition ist, dann ist das eben so.
public interface Utilities {
BigDecimal berechneBrutto(BigDecimal netto);
}
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public final class UtilitiesImpl implements Utilities {
@Inject
private UtilitiesImpl() {
}
@Override
public BigDecimal berechneBrutto(final BigDecimal netto) {
return netto.multiply(new BigDecimal(Math.random()));
}
}
import java.util.List;
import javax.inject.Inject;
public final class Usecase {
private final Utilities utilities;
@Inject
private Usecase(final Utilities utilities) {
this.utilities = utilities;
}
public BigDecimal summiereBrutto(final List<BigDecimal> nettoValues) {
BigDecimal result = new BigDecimal(0);
for (final BigDecimal netto : nettoValues)
result = result.add(utilities.berechneBrutto(netto));
return result;
}
}```
```import static org.testng.Assert.assertEquals;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import org.testng.annotations.Test;
import mockit.Expectations;
import mockit.Injectable;
import mockit.Tested;
public final class UsecaseTest {
@Tested private Usecase use;
@Injectable private Utilities utilities;
@Test
public void summiereBrutto() {
final List<BigDecimal> input = Arrays.asList(new BigDecimal(3), new BigDecimal(5), new BigDecimal(7));
final BigDecimal expected = new BigDecimal(15);
new Expectations() {
{
utilities.berechneBrutto(input.get(0));
returns(input.get(0));
utilities.berechneBrutto(input.get(1));
returns(input.get(1));
utilities.berechneBrutto(input.get(2));
returns(input.get(2));
}
};
assertEquals(use.summiereBrutto(input), expected);
}
}```
Egal was in der Methode berechneBrutto passiert, der Test von summiereBrutto ist unabhängig davon. Wie würdest Du den summiereBrutto in diesem Beispiel testen?
Gruß
Fancy
[quote=SlaterB]dort keine Lust das interne FeldGeometrie simpel passend in einer Zeile zu definieren,
stattdessen mega kompliziert vorhergesehen, welche Methoden benötigt werden und diese das richtige zurückgeben lassen?[/quote]Da muss nichts “vorhergesehen” werden:
Nicht oder falsch konfigurierte Mocks führen zu NPEs.
Außerdem soll man den Test nicht als Test, sondern als “ausführbare Spezifikation” verstehen, und in dieser Hinsicht ist es durch aus sinnvoll aufzuzeigen, welche Rückgabewerte aus den Abhängigkeiten für den aktuellen Testfall relevant sind.
[quote=SlaterB;131444]aber die letzten beiden haben gar keine Funktion, Rückgabe egal, nur Dummy-mäßig ergänzen damit der Ablauf nicht an dieser Stelle streikt…,[/quote]eben. Der Test dokumentiert, dass die Unit (Spiel) mit den vom ZustandsFinder zurückgegebenen Objekten arbeitet. Das ist was anderes als “gar keine Funktion, Rückgabe egal, nur Dummy-mäßig ergänzen”.
dein Beispiel ist konsequent, indem überhaupt nicht gerechnet wird, explizit eine von Anfang an falsche Summe, nur die Normalwerte addiert,
wäre es dann nicht als weitere Vereinfachung noch denkbar, den Brutto-Rechner im ersten Aufruf das Endergebnis, für alle anderen Aufrufe 0 zurückgeben zu lassen…?
ich sehe das Konzept dahinter, danke, aber sinnvoll ist das in meinen Augen nicht, hier auch das von mir genannte ‚extrem aufwendig‘ mit Vorgabe der Rückgabewerte,
nur
assertEquals(use.summiereBrutto(input), new BigDecimal("19.57"));
wäre ein einfacherer und echter Test
oftmals geht es eindeutig nicht ohne Mocken wie DB-Login, aber solche Simpel-Methoden…,
wenn es keine Standard-Utilities-Implementation gäbe, oder dieser wieder selber komplex aus DB/ Usereingabe zu konfigurieren wäre, dann wieder ein ähnlicher Grund,
aber es geht hier zurück auf den Anfang des Themas, statisch-verdächtige einzelne Methoden wie Sortierung
neben der irrsinnigen Vorgabe-Komplexität ist ein Problem auch die massive Abhängigkeit vom internen Aufbau,
man könnte nicht das Spiel intern umbauen, Verzicht auf feldGeometrie.zellen(), neue Methoden und anderes, ohne auch den Test exakt genauso anzupassen,
zwar wird der Test meckern, man merkt die Fehler und ist zur Korrektur gezwungen, keine Gefahr, nur Arbeit,
aber das ist ja so ewig weit von Blackbox-Test entfernt…,
viel sinnvoller ist doch wenn möglich ein Test nur mit Übergabe der korrekten Daten feldGeometrie.setzeKarte("123 456 789 "); und Prüfung der korrekten Ergebnisse,
ohne jeden einzelnen Methodenaufruf auch im Test genauso aufzuschreiben, funktioniert mit verschiedenen Implementierungen des Spiels, mit verschiedenen Versionen, ein simpler Test
bei so massiven Eingriff auch Gefahr, dort zum erfolgreichen Test passende Zwischendaten zurückzugeben, die es real nie geben könnte,
etwa Rückgabe einer Nachbarliste von 9 Elementen, obwohl technisch nicht mehr als 8 möglich sind?
wer testet dass man den Test nicht falsch macht?
gilt strenggenommen für Mock zu erfolgreichen DB-Login oder Rückgabe der Nachbar-Liste aus DB ersetzt genauso,
aber da hat man guten Grund zu ersetzen, geht nicht anders, ohne Notwendigkeit doch besser einsparen
edit: beim Mocken wenn möglich besser auch sinnvolle Komponenten bauen, etwa mit etwas Mühe eine die DB-Login zwar ohne Datenbankzugriff immer erfolgreich durchführt,
aber durchaus auch intelligent merkt, welche User schon eingeloggt sind, und das für spätere Zugriffe nutzt, nur dieser User darf weiteres, andere nicht ohne auch Login,
statt Dummy-Methoden zu schreiben die ohne Denken alles jeweils passend akzeptieren,
[QUOTE=SlaterB]ich sehe das Konzept dahinter, danke, aber sinnvoll ist das in meinen Augen nicht, hier auch das von mir genannte ‘extrem aufwendig’ mit Vorgabe der Rückgabewerte,
nur
assertEquals(use.summiereBrutto(input), new BigDecimal("19.57"));
wäre ein einfacherer und echter Test[/QUOTE]
Nein, in der von mir gegebenen berechneBrutto steckt ein statischer Methodenaufruf, der verhindert das Dein Test so funktioniert. Oder ein anderer:
public BigDecimal berechneBrutto(final BigDecimal netto) {
final GregorianCalendar cal = new GregorianCalendar();
if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
return netto.multiply(new BigDecimal(1.07));
else
return netto.multiply(new BigDecimal(1.19));
}```
Dann funktioniert Dein Test an Tagen an denen Du arbeiten musst, beim Bäcker auch, an der Tankstelle aber nicht mehr. ;)
berechneBrutto kann schon während der ersten Implementierung falsch sein, oder seine Bedeutung im Laufe der Zeit ändern. Willst Du dann alle Tests die von berechneBrutto abhängen jedes mal ändern? Wie viele sind das?
Wenn Du das so machst wie in meinem Beispiel, dann brauchst Du nur einen Test anzupassen, den von berechneBrutto.
Gruß
Fancy
gewiss, wenn man das Ergebnis nichtmal mit Menschengehirn vorhersagen kann dann ist normaler Test recht unmöglich,
wobei hier schon der Einzeltest von berechneBrutto() seine Schwierigkeiten hätte, für Einkaufskorb-Frage wieder etwas unpassend,
ich setzte eine erfolgreich getestete berechneBrutto()-Methode voraus und fragte, zu welchem Zweck man diese mocken müsste für Einkaufskorb-Summe
wie viele Tests und Änderungen es sind wird sich jeweils zeigen, aber was schadet es, auch in ein paar mehr Tests die Berechnung sauber inhaltlich zu testen?
Test schlägt fehlt, mit neuen Zins manuell Ergebnis errechnen, als Zielwert einsetzen, Test geht, bestens
edit:
falls berechneBrutto() zu einer größeren Komponente mit Konfiguration des aktuellen Wochentags usw. wird, dann ist das wieder eines der bekannten Mock-Themen,
größeren Aufwand abkürzen, hier zwar noch denkbar
Rechner n = new Rechner(u);
BigDecimal ergebnis = n.summiereBrutto(einkaufskorb); ```
aufzubauen, nur wenige Zeilen, bei Alternative Mocken muss man so oder so darüber nachdenken, welche Abhängigkeiten wie betroffen sind,
aber irgendwann sicherlich zu allem der Punkt denkbar, an dem man das nicht mehr will
eine der bekannten Mock-Arten,
aber dagegen einfache feste Logik-Implementierung ohne Not durch aufwendige Liste von Dummy-Ergebnissen ersetzen?..
-----
durch Mocken derart reduziert wird wie ja allen bekannt nur die Addition getestet,
ähnlich bös angenommen könnte die Implementation von summiereBrutto() versehentlich
``` BigDecimal result = new BigDecimal(0);
for (final BigDecimal netto : nettoValues) {
BigDecimal brutto = utilities.berechneBrutto(netto)
result = result.add(netto);
}
return result;
lauten
dann würde der Mock-Test erfolgreich die Netto-Addition testen, die intern auch passiert, Test erfolgreich, aber Implementierung eigentlich kaputt…,
in dem Test-Beispiel nur mit Ganzzahlen als Eingaben und Ganzzahl-Summe könnte zudem die Cent-Summierung kaputt sein, man weiß es nicht, testet es nicht
besser wenn überschaubar möglichst lebensechten Blackbox-Test:
sinnvolle Werte übergeben und soweit möglich intern real rechnen lassen und Ergebnis prüfen,
da paar komplizierte Zahlen nebenher ausrechnen und eingeben kostet nicht viel
edit:
ein nicht funktionierender Test ist nie ein größeres Problem, sondern zeigt wo evtl. wichtig zu findender Fehler steckt oder wo der Test noch nicht ausreicht,
tatsächlich vielleicht mal mit Anlass Mocking braucht
lieber 100 fehlgeschlagene und zu ergänzende Tests bei richtiger Implementation
als auch nur ein einfacher Test der erfolgreich ist und dabei einen Implementierungsfehler durchläßt, das ist nämlich dann ein gigantisches Problem!