[quote=SlaterB]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]
Ja genau das heißt es, wo bei Dein Beispiel hier für mich eine mögliche Ausnahme wäre (siehe später). Nochmal, ich will kein Playdoyer gegen statische Methoden halten. Sondern gegen die unbedachte Verwendung in Clientcode. Wenn ich mir externe Abhängigkeiten in meine Packages hole, überlege ich mir sehr gut wie. Bei (Standard-)Klassen des JDK und bei überragenden Libs (z.B. die von Apache) bin ich da großzügiger, bei allen anderen programmiere ich mir Package-Private "Wunsch-"Interfaces, die über externe Abhängigkeiten implementiert werden können oder auch anders (wie’s gerade nötig ist). Auf die Weise sind die Abhängigkeiten im Client Code nicht sichtbar und zentral an wenigen Stellen zu pflegen.
[quote=SlaterB;131413]widerspricht das nicht eigentlich auch
?[/Quote]
Ja, tut es. Ich bin aber auch der Meinung, dass Unit-Tests mehr als reine Blackbox-Tests sein müssen. Aber das ist ein anderes Thema und dazu war ich hier auch schon in Diskussionen verwickelt ;-)…
jede Methode enthät immer für sich Code, der dann genausowenig einzeln mockbar ist,
solchen Code in eine statische Methode auszugliedern kann es nicht schlecher machen,
noch weniger wenn damit doppelten Code in verschiedenen Methoden eingespart, uneingeschränkt erstmal besser
als weiteren Schritt könnte man dann natürlich diese statische Methode noch in eine nicht-statische umwandeln, mit Interface, Austauschbarkeit, Mockbarkeit usw.,
ganz klar ein Fortschritt, nur mit teils bedeutenden Aufwand verbunden, nicht unbedingt nötig für jede kleine Methode des Programms,
wie auch nicht jeder Teil-Code von Methoden in eigene Methoden ausgelagert wird (wobei ja kurze Methoden ein Thema für sich sind )
also sind statische Methoden nicht per se schlecht, sondern Schritt 2 der Stufe, welche evtl. noch weiter geschickt werden könnten, ruhig erst bei konkreten Anlass
Unit Tests sind meistens whitebox tests, zumindest solange irgendeine Art von Mock/Stub/Fake oder sonstigem zum Einsatz kommt,alleine schon um einen Konstruktor aufzurufen muss man schon ziemlich viele uerb die internas der zu testenden Klasse/Objktes wissen wie zB. Abhaengigkeiten.
Verhalten zu mocken und verifizieren, also das Methoden der Mocks in einer bestimmten Reihenfolge aufgerufen werden muessen ist ein Weg, ein anderer ist Zustand zu verifizieren, also Werte, mischen geht auch aber die tests sind dann sehr fragil.
Wenn es um Math.x geht dann macht wohl nur Zustandsverifizierung Sinn, aber das ist in Geschaeftsanwendungen eher die Ausnahme Math. zu mocken, doofes Beispiel IMHO weil unrealistisch/irrelevant (nicht boese gemeint).
Na, gut dass das nicht im OT-Tag eines anderen Threads versauert ist - da gibt’s ja offenbar schon einiges zu sagen…
Finde ich nicht. Ich finde, genau darum geht es: Was wird getestet und was gemockt? Im nächsten Satz stand es ja dann auch:
Ich denke, das ist der entscheidende Punkt - also der, wo die „Schere“ zwischen den unterschiedlichen Ansichten aufgegangen ist. Oder, wo es noch konkreter wurde:
Das ist einleuchtend und für bestimmte Dinge sinnvoll, und das Beispiel mit dem new BigDecimal(Math.random()) sehr überzeugend. Es ist klar: Ein Unit-Test sollte eine Unit testen. Oder, um es als eins der üblichen Qualitätskriterien für Tests zu formulieren: Es sollte klipp und klar abgegrenzt sein, WAS genau der Test testet. In den oben angedeuteten Beispielen sollte die Aufsummierung der brutto-Werte getestet werden, und NICHT die „berechneBrutto“-Funktion.
Das alles hat aber nichts mit statischen Methoden zu tun. Eine statische Funktion hat keinen Zustand, keine Abhängigkeiten, und vor allem: Keine unterschiedlichen Implementierungen. Es spielt keine Rolle, ob man new Expectations() { returns(4); } oder new Expectations() { returns(Math.pow(2,2)); }
schreibt.
Deswegen leuchtet mir das auch nicht ein:
[QUOTE=Fancy;131466]Nein, in der von mir gegebenen berechneBrutto steckt ein statischer Methodenaufruf, der verhindert das Dein Test so funktioniert. Oder ein anderer:
…
[/QUOTE]
Wer so eine statische Methode schreibt ist selbst Schuld (zustandsbehaftet, nichtdeterministisch). Es geht (bzw. ging mir) um Methoden die echte Funktionen (im mathemtatischen Sinn) sind.
Unabhängig davon finde ich auch eines der weiter oben von SlaterB genannten Argumente nicht von der Hand zu weisen: Wenn der Production-Code eine Methode verwendet (eigentlich egal, ob statisch oder nicht), und in seiner Verwendung dieser Methode eine Annahme steckt, dann kann es passieren, dass der Test (dadurch, dass die Funktion weggemockt ist) durchläuft, obwohl er im Production-Code NICHT richtig laufen wird. Natürlich wäre es in bezug auf die abstrakten Qualitätskriterien für Tests „schlecht“, wenn mit der „summiereBrutto“-Methode die Methode „berechneBrutto“ stillschweigend „mitgetestet“ wird. Aber das ist nur eine Ausprägung von etwas, was ich ansonsten für selbstverständlich halten würde (auch wenn ich kein „Testexperte“ bin). Nämlich dass man sich innerhalb eines („grobgranulareren“) Tests auf die Dinge verlassen kann (und können MUSS!) die innerhalb von anderen („feingranulareren“) Tests schon abgedeckt sind.
Ich denke, niemand stellt in Frage, dass Mocking sinnvoll sein kann, um den Test „gut“ zu machen, in dem Sinne, dass man NUR das testet, was in diesem Test getestet werden SOLL. Aber mit static Methods hat das alles nichts zu tun…
@Fancy und ich reden von Tests für Units, die andere Units benuzten. Da mocken wir (also @Fancy und ich) alle Units, die die aktuelle Unit verwendet, weg und bestimmen für jeden einzelnen Testfall, wie die einzelnen Abhängigkeiten reagieren sollen, wenn mit ihnen interagiert wird (und ob das überhaupt passieren soll…)
Du redest von einem Test, der andere Methoden in der selben Unit benutzt.
In Deinem Fall, wenn es keine weiteren Kollaborateure gibt, muss natürlich auch nichts gemockt werden. Auch nicht die Methoden, die innerhalb der Unit aufgerufen werden, denn diese stellen ja tatsächlich die Funktionalität der Unit bereit.
[quote=SlaterB]besser wenn überschaubar möglichst lebensechten Blackbox-Test:
sinnvolle Werte übergeben und soweit möglich intern real rechnen lassen und Ergebnis prüfen[/quote]Auf jeden Fall, wenn diese Unit nicht mit anderen Units interagiert.
Ich denkte Du hängst immer noch an der Vorstellung, das man jede Methode, auch die in der selben Unit, die gerade getestet wird mocken sollte. Das stimmt aber nicht. Die einzigen Methoden, die in der getesteten Unit selbst gemockt werden sollten ist solche, die eine Abhängigkeit als Rückgabewert haben, die man andernfalls nicht ersetzten kann.
Beispiel wäre, wenn meine zu testende Unit Objekte anderer Klassen im Construktor selbst instanziiert: public class RechnungsDrucker{ private final Rechner r = new RechnerImpl(); Rechner getRechner(){ return r;} public String drucke(Einkaufskorb ek){ Rechner rechner = getRechner(); // .... } }Hier würde man den (package-private) Getter mocken, um die echte RechnerImpl durch ein Mock zu ersetzten, damit man die korrekte Interaktion mit dieser Abhängigkeit prüfen kann.
Das kleine Beispiel zeigt aber auch die Probleme mit diesem Ansatz: Wer garantiert, dass die drucke()-Methode nicht direkt auf die Membervariable r zugreift, oder (nur um den Test zu befriedige) beide Referenzen parallel benutzt? Aber IoC und DI sind schon wieder ganz andere Themen…
Hier mal etwas Code den ich am Wochenende genutzt/geschrieben habe, bzw. geschrieben hätte, wenn ich nicht DI und Unit Tests nutzen würde:
/***************************************************************
* fft.c
* Douglas L. Jones
* University of Illinois at Urbana-Champaign
* January 19, 1992
* http://cnx.rice.edu/content/m12016/latest/
*
* fft: in-place radix-2 DIT DFT of a complex input
*
* input:
* n: length of FFT: must be a power of two
* m: n = 2**m
* input/output
* x: double array of length n with real part of data
* y: double array of length n with imag part of data
*
* Permission to copy and use this program is granted
* as long as this header is included.
****************************************************************/
public static void fft(final double[] x, final double[] y) {
int i, j, k, n1, n2, a;
double c, s;
double t1, t2;
final int n = x.length;
final int m = (int) (Math.log(n) / Math.log(2));
// Bit-reverse
j = 0;
n2 = n / 2;
for (i = 1; i < n - 1; i++) {
n1 = n2;
while (j >= n1) {
j = j - n1;
n1 = n1 / 2;
}
j = j + n1;
if (i < j) {
t1 = x**;
x** = x[j];
x[j] = t1;
t1 = y**;
y** = y[j];
y[j] = t1;
}
}
// FFT
n1 = 0;
n2 = 1;
for (i = 0; i < m; i++) {
n1 = n2;
n2 = n2 + n2;
a = 0;
for (j = 0; j < n1; j++) {
c = Math.cos(-2 * Math.PI * a / n);
s = Math.sin(-2 * Math.PI * a / n);
a += 1 << (m - i - 1);
for (k = j; k < n; k = k + n2) {
t1 = c * x[k + n1] - s * y[k + n1];
t2 = s * x[k + n1] + c * y[k + n1];
x[k + n1] = x[k] - t1;
y[k + n1] = y[k] - t2;
x[k] = x[k] + t1;
y[k] = y[k] + t2;
}
}
}
}
}```
```public class AnalyzerImpl {
public boolean isSignal(final double[] input, final double[] reference) {
final double[] re = new double[input.length];
final double[] im = new double[input.length];
System.arraycopy(input, 0, re, 0, input.length);
FFT.fft(re, im);
for (int i = 0; i < input.length / 2 - 1; i++)
if (reference** < re[i + 1] * re[i + 1] + im[i + 1] * im[i + 1])
return false;
return true;
}
}```
fft ist eine Funktion, genau wie Du sie beschreibst. Wie hätte ich isSignal testen sollen? Mir ein reference Signal ausdenken und die fft im Kopf zurückrechnen, damit ich isSignal mit passenden Argumenten aufrufen kann?
Ein Mock ist da einfach einfacher. Und eine statische Methode zu mocken ist *würg*.
Gruß
Fancy
Ich weiß ja nicht genau, was „isSignal“ ist oder machen soll, aber sie sieht erstmal SELBST wie eine Testfunktion aus.
Ich denke auch, dass an einigen Stellen hier aneinander vorbei geredet wird, wobei unterschiedliche Interpretationen und Blickwinkel eine Rolle spielen.
Es könnte schwierig sein, diese Interpretationen und Blickwinkel „anzugleichen“, oder genauer zu identifizieren, wo sie herkommen und wie sie die gemachten Aussagen beeinflussen. Beispiele KÖNNEN helfen, aber erstens sind diese Beispiele meistens sehr künstlich, oder beleuchten auch nur eine Facette, oder bewirken, dass man sich an dem Beispiel aufhängt und Details mit „Ja, aber…“ - Sätzen zerpflückt.
Die bisher angedachten Beispiele sind aus einem Blickwinkel geschrieben, um eine Facette zu beleuchten. Nochmal die andere Richtung, natürlich ebenso suggestiv: Man hat eine Klasse wie die hier
class DefaultStatistics implements Statistics
{
@Override
public double computeSum(List<Double> list) { ... }
@Override
public double computeAverage(List<Double> list) { ... }
}
Die kann man nun ausimplementieren:
class DefaultStatistics implements Statistics
{
@Override
public double computeSum(List<Double> list)
{
double sum = 0;
for (int i=0; i<list.size(); i++)
{
sum += list.get(i);
}
return sum;
}
@Override
public double computeAverage(List<Double> list)
{
double sum = 0;
for (int i=0; i<list.size(); i++)
{
sum += list.get(i);
}
return sum / list.size();
}
}
Und worauf ich hinaus wollte (und was auch SlaterB so ähnlich angedeutet hat) ist, dass es besser wäre, das zu schreiben als
class DefaultStatistics implements Statistics
{
@Override
public double computeSum(List<Double> list)
{
return sum(list);
}
@Override
public double computeAverage(List<Double> list)
{
return sum(list) / list.size();
}
private static double sum(List<Double> list)
{
double sum = 0;
for (int i=0; i<list.size(); i++)
{
sum += list.get(i);
}
return sum;
}
}
Ieeeeh, Hilfe, eine static-Methode, die kann man ja nicht mocken!!! Na, und, das ist doch kein Problem, oder?
Also, spricht da nun faktisch-fachlich etwas dagegen? Ich denke, nicht…
[quote=Marco13]Also, spricht da nun faktisch-fachlich etwas dagegen? Ich denke, nicht…[/quote]Richtig:
die static Methode in diesem Beispiel gehört nicht zum öffentlichen Interface der Klasse und muss deshalb auch nicht “mockbar” sein.
Ich persönlich würde diese Methode zwar nicht static machen, aber dass ist (solange die Methode “stateless” bleibt) eine Frage des persönlichen Geschmacks…
OK.
So weit, so gut.
Nun der nächste Evolutionsschritt.
Man hat eine weitere Klasse
class DefaultSummaries implements Summaries {
@Override
public double computeAverageValueOf(List<Double> values) { ... }
}
die man implementieren muss. Sollte man nun
den nötigen Code dort “inline” reinschreiben?
eine weitere private statische Methode erstellen (die GLEICHE, wie die in der “DefaultStatistics”-Klasse)?
Oder einfach schreiben
class DefaultSummaries implements Summaries {
@Override
public double computeAverageValueOf(List<Double> values)
{
return Utils.sum(values) / values.size();
}
}
class Utils
{
static double sum(List<Double> list)
{
double sum = 0;
for (int i=0; i<list.size(); i++)
{
sum += list.get(i);
}
return sum;
}
}
und diese Methode dann auch in der “DefaultStatistics” aufrufen?
(Der nächste “Schritt” wäre dann, die Utils und ihre Methode “public” zu machen, aber das wäre ein GROSSER Schritt, der SEHR genau überlegt sein sollte - aber mit Test+Mock wiederum nichts zu tun hat)
Dass die Frage nach Testbarkeit und Mockbarkeit STARK mit der Frage nach dem public Interface, der Überschreibbarkeit von Methoden usw. zusammenängt, hatte ich ganz von Anfang an betont, aber das ist wohl in dem (in diesem Sinne wohl schlicht nicht ausreichend ausdifferenzierten) “static ist böse weil nicht mockbar”-Tumult untergegangen…
Die Methode oben ist eine simple Geräuscherkennung. input kommt von einem Mikrofon, reference aus einer Datenbank. Ist die Frequenzverteilung des Referenzsignals in den Mikrofondaten enthalten gibt die Methode true zurück, sonst false.
Um isSignal zu testen, brauchst Du entweder anschauliche Testdaten, die sich mit einem Blick drauf nachvollziehen lassen (schwierig weil FFT komplex ist) oder Du ersetzt die FFT durch einen Mock und nimmst z.B. die Identische Abbildung. Dann wird der Test trivial. Aber eben nur wenn FFT nicht statisch implementiert ist.
Bei Deinem Summen Beispiel, wäre private static für mich akzeptabel, solange die Methode Zustands- und Seiteneffekt-frei ist. Machen würde ich das aber nicht. Package private wäre mir aber schon zu viel. Eventuell akzeptabel, wenn das Package in einem eigenen JAR steckt und „Sealed: true“ im Manifest festgelegt wurde. Machen würde ich das aber auch nicht.
Meine Lösung wäre klassisch: Interface mit nicht statischer Implementierung und als Singleton Scope injizieren.
[quote=Marco13]die man implementieren muss. Sollte man nun
den nötigen Code dort “inline” reinschreiben?[/quote]Nein.
[quote=Marco13;131500]- eine weitere private statische Methode erstellen (die GLEICHE, wie die in der “DefaultStatistics”-Klasse)?[/quote]Nein.
[quote=Marco13;131500]- Oder einfach schreiben
[Java Code: mit static access in neuer Util-Klasse][/quote] fast.
[quote=Marco13;131500](Der nächste “Schritt” wäre dann, die Utils und ihre Methode “public” zu machen, aber das wäre ein GROSSER Schritt, der SEHR genau überlegt sein sollte [/quote]Jetzt bin ich neugierig: Welche Argumente sprechen den dagegen eine “Util”-Klasse und deren Methoden nicht public zu machen?
[quote=Marco13;131500]- aber mit Test+Mock wiederum nichts zu tun hat)[/quote]Doch.
Aber das ist für jemanden, der static Methoden schreibt einfach weil er es kann und für den Tests nichts weiter als Korrektheitsbeweise sind, nur schwer zu verstehen.
Der Punkt ist, dass diese neue Util-Klasse eine (neue) Abhängigkeit für beide Ausgangsklassen dar stellt.
Wenn man den Test nicht nur als Korrektheitsbeweis, sondern auch als Spezifikation und Beschreibung der getesteten Unit versteht, will man darin alle Abhängigkeiten deutlich machen. Also wird die Methode eben nicht-statisch (was nicht notwendiger weise auch “public” bedeutet) und die anderen Klassen bekommen die Util-Klasse injiziert:```class Utils
{
double sum(List list)
{
double sum = 0;
for (int i=0; i<list.size(); i++)
{
sum += list.get(i);
}
return sum;
}
}``````class DefaultStatistics implements Statistics
{
private final Util util;
DefaultStatistics(Util util){this.util = util;}
@Override
public double computeSum(List<Double> list)
{
return util.sum(list);
}
@Override
public double computeAverage(List<Double> list)
{
return util.sum(list) / list.size();
}
}```OK, damit das funktioniert muss man (mindestens) eine Instanz von Util erzeugen, die vorher nicht gebraucht wurde. Was für eine Katastrophe in einer OO-Sprache… ;o)
bye
TT
*** Edit ***
[quote=Sym;131503]DefaultStatistics liefert doch computeSum(…). Warum nutzt Du diese nicht einfach?[/quote]Beispiele hinken immer, sei nicht so kleinlich.
[quote=Sym;131503]Wieso ist computeAverageValueOf in DefaultSummaries eigentlich nicht statisch, deine sum in Utils jedoch schon?[/quote]Weil die vom Interface geforderten Methoden nicht static sein können.
Aber selbst wenn: siehe oben…
Es ist nicht nötig, hier noch einen Nebenkriegsschauplatz auf zu machen.
Weil die vom Interface geforderten Methoden nicht static sein können.
Aber selbst wenn: siehe oben…
Es ist nicht nötig, hier noch einen Nebenkriegsschauplatz auf zu machen.
bye
TT[/QUOTE]
Ich versuche nur das Beispiel zu verstehen. Und mit TDD gäbe es diese Situation meiner Meinung nach in dieser Form nicht. Statische Mocks verwende ich z.B. selten bzw dann, wenn ich durch Nutzung der Libs dazu gezwungen werde. Zudem ist das Mocken von statischen Methoden (und die Möglichkeiten) immer sehr vom gewählten Framework abhängig.
War da eine Verneinung zu viel drin? Ich kapier’ die Frage gerade nicht. Wenn das interne Utilities sind, die nicht Teil der public API sein sollen, dann macht man sie natürlich nicht public.
Und das ist ja auch einer der entscheidenden Punkte:
Man bekommt ein „Statistics“-Objekt. Im „Statistics“-Interface steht, was das Ding macht, und das ist das einzige, was für JEDEN, der das verwenden will, relevant ist. Man weiß nicht, dass das ein „DefaultStatistics“ ist. Man weiß nicht, ob die „computeSum“ direkt oder mit einer privaten statischen Methode oder mit einer package-privaten Methode einer anderen (package-privaten) Klasse implementiert ist. Niemand sieht das. Niemand weiß das. Niemand kann von außen den Unterschied feststellen. Man sieht auch von außen nicht, ob da ein Utils-Objekt injiziert wurde. Es ist eine Black box. Und trotzdem soll man („unbedingt“) ein Utils-Objekt mit Dependency injection da reinwursten? Ich sehe da immernoch schlicht keinen Grund. Es geht dabei nicht um eine Objekterzeugung. Das, was mich daran stört ist: Das Objekt erfüllt keinen Zweck. Es ist vollkommen egal, ob man da ein Utils-Objekt injiziert oder eine statische Methode aufruft. Es gibt keinen Unterschied.
(Ja, man könnte ein anderes Utils-Objekt da rein-injizieren. Wo die „sum“-Methode anders implementiert ist. Aber jeder, der das (aus welchem Grund auch immer) machen würde, könnte genausogut eine andere (statische) Methode aufrufen. Niemand wird von außen den Unterschied erkennen können).
Nuja, sobald Du Deine statische Util Methode offener als private machst, erreicht sie für mich eine Sichtbarkeit die mich persönlich stören würde. Andererseits, wenn die Methode private ist, kann sie nur von einer Klasse verwendet werden. Denselben Code in mehren Klassen zu kopieren ist keine Option. Eine normale zustandslose Klasse die für die Util Methode jedes mal neu instantiiert werden muss, ist auch keine Option. Singleton Scope halt ich dafür für die sauberste Lösung.
Außerdem wird es eben beim Testen schwierig, sobald die Funktion die abgebildet wird komplexer ist. Siehe mein Beispiel mit der FFT. Die FFT alleine ist einfach zu testen. Die isSignal alleine ist einfach zu testen. Testet man beiden zusammen, braucht man aber Testdaten die nicht mehr trivial sind. Hinzukommt, das Fehler schwieriger zu lokalisieren sind und das man eben den Test erst Laufen lassen kann wenn alle beteiligten Klassen fertig sind.
Du müsstest also im Einzelfall entscheiden ob die Methode die Du gerade schreiben willst klein genug für eine statische Util Methode ist, oder komplexer das sie etwas mehr braucht. Ich mag solche Einzelfall Entscheidungen nicht. Je homogener der Code ist, desto besser. Keine Extrawürste für kleine Scheißer.
Deshalb lasse ich mir auch den Logger injizieren, obwohl fachlich nichts gegen die Factory spricht.
Das Beispiel wie ich es in #15 geschrieben habe skaliert, ich muss dabei nicht nachdenken. Das ist monoton und immer dasselbe, das kannste so auch an einem schlechten Tag runter schreiben, ohne das da was schief geht.
Warum wuerdest du sie „natuerlich nicht public machen“?
Es kommt halt wirklich darauf an ob man den Code testbar machen will oder nicht… mit „public“ Objekten die Injiziert werden ist mocken sehr einfach, mit privaten und/oder statischen Methoden eben nicht…
[quote=Marco13](Ja, man könnte ein anderes Utils-Objekt da rein-injizieren. Wo die “sum”-Methode anders implementiert ist. Aber jeder, der das (aus welchem Grund auch immer) machen würde, könnte genausogut eine andere (statische) Methode aufrufen. Niemand wird von außen den Unterschied erkennen können).[/quote]Wenn Du die Summe in einer statischen Methode bildest musst du alle Klassen, die die statischen Methoden verwenden, ändern, weil eine weitere Implementierung hinzu kommt. (Und sehr wahrscheinlich auch deren Tests.)
Wenn die Summe als Abhängigkeit hineininjiziert wird, kann man die Summenbildung ändern, ohne den Code der Statistics-Implementierung (oder deren Tests) anfassen zu müssen.
Und richtig schwierig wird’s, wenn Du beide Summenbildungen parallel brauchst: mit statischen Methoden musst Du entweder 2 verschiedene Implementierungen des Statistic interfaces machen oder innerhalb der Stattistik-Implementierung verzweigen, womöglich anhand eines von außen übergebenen Parameters.
Sag “bye bye” zu IoC und DI…
Wie auch immer: In Deinen Tests für die Statistik -Implementierung musst Du beide Varianten berücksichtigen: Jede Funktionalität, die Deine Statistik-Implementierungen bieten muss mit jeder Utility-Methode getestet werden.
Testaufwand mal eben für jede Klasse, die die statischen Methoden verwendet, verdoppelt, oder verdreifacht, falls jemandem noch eine weitere Summenmöglichkeit einfällt.
Das ist genau die Sorte von Aufwand, die ich in meinen Projekten spare.
Benutzen meine Statistik-Implementierungen dagegen einfach eine nicht statische Methode in dem Util-Objekt, dass sie injiziert bekommen, müssen weder neue Tests noch zusätzlicher Code darin geschrieben werden. Lediglich für die neue Utility-Implementierung mit der neuen Summenbildung muss ich Tests schreiben. Dazu kommen noch Änderungen an den stellen, wo doe Statistik-Implementierungen und die Utility-Implementierungen instantiiert werden, das ist aber hoffentlich eine zentrale Stelle, wenn das nicht sogar ein Framework übernimmt…
Die Funktionalität des Utilitys wird für die Tests der Statistik-Implementierungen durch ein Mock ersetzt so dass dessen Implementierung nicht mehr relevant ist.
Eigentlich ist das genau das Parade-Beispiel, warum static Methoden schlecht sind: Sie verbauen den Weg für Polymorphie und IoC/DI.
Aber gut: es gibt tatsächlich einen Fall, wo ich selbst von meiner static-Verteufelung ablasse:public class Nullable{ public Nullable whenNotNull(Object value){return new Nullable(value);} private final Object value; private Nullable(Object value){this.value=value;} public <T> T or(Object alternative){ return null!=value?value:alternative;} }
und zwar, weil die static Methode lediglich ein Wrapper für den Constructor ist (der ja im Prinzip auch eine statische Methode ist…) und es sich im Code einfach besser liest wenn man MyType notNullValue = whenNotNull(valueFromDatabase) .or(someDefaultValue); schreibt statt MyType notNullValue = new WhenNotNull(valueFromDatabase) .or(someDefaultValue);
Vemutlich hätte ich diesen Satz einfach weglassen sollen. Ich könnte jetzt zwar mit Pseudo-Gegenargumenten kommen, wie etwa, dass sich an dem „Utils“ etwas ändert, und das dann ja auch nicht mit DI abgebildet werden kann, aber das würde alles wieder auf eine falsche Schiene führen.
Einer von uns beiden kapiert etwas einfach nicht. Ich würde nicht anzweifeln, dass ich das bin.
Aber machen wir’s so: Ich gebe dir zwei .class-Dateien. Jede enthält eine Klasse. Beide implementieren „Comparable<List>“, für eine lexikographische Sortierung. Dafür schreibst du dann einen Unit-Test. Und dann sagst du mir, welche der beiden die „schlechte“, „unflexible“ mit dem bösen static ist.