Testing von Interfaces

EDIT: Abgetrennt von https://forum.byte-welt.net/java-forum/allgemeine-themen/22264-tdd-testet-interfaces.html

Obwohl ich kein Testing-Experte bin, finde ich, dass man gerade für interfaces Test schreiben sollte. Die definieren ja genau den Contract. Und mit dem Test kann sichergestellt werden, dass jede Implementierung den Contract erfüllt.

(Dass das schwierig sein kann hatte ich u.a. mit Verweis auf sowas wie https://chromium.googlesource.com/external/guava-libraries/+/v15.0/guava-tests/test/com/google/common/collect/ListsTest.java schon an anderer Stelle ausgebreitet…)

[quote=Marco13]Die definieren ja genau den Contract.[/quote]Ja.

[quote=Marco13;141276]Und mit dem Test kann sichergestellt werden, dass jede Implementierung den Contract erfüllt.[/quote]nein. Man kann nur die Implementierung selbst darauf testen, dass sie den Contract erfüllt.

bye
TT

[quote=SlaterB]man kann jede Implementierung den Test durchlaufen lassen…,[/quote]OK, da sind wir und wieder einig… ;o)

Ich finde die Diskussion sehr hilfreich.

Also testet man das Interface “mit”, wenn (all) seine Implementierungen (oder die erste) getestet werden. Das macht Sinn. Im obigen Fall hatte ich in einem neuen Projekt mit dem Interface angefangen und wollte daher zuerst dessen Test schreiben. Wenn ich mit der ersten Implementierung (bzw. deren Test) angefangen hätte und dort das Interface vorkommt, hätte ich es auch mit “erzwungen”. Und eben mit getestet, ohne extra Testklassen für das Interface.

das klingt für mich wenig zielführend, als wenn man bei einer Methode

/**

  • Gibt die Summe von a und b.
    */
    double y sum(double a, double b)

einerseits den Vertragstext im Kommentar als dann auch getrennt die Implementierung testen würde…,
dazu schreibt man keine zwei Tests (naja, bei TDD weiß man nie…), genausowenig einen ‚Test des Interfaces‘

für die Kategorie der ‚sinnvollen Tests‘ :wink: ist es egal ob ein Interface oder eine einzelne Klasse vorliegt oder ein Test
nacheinander für verschiedene Subklassen eines in Oberklasse definierten Verhaltens (Polymorphie ganz ohne Interface) ausgeführt wird,
genau so wie es auch im Java-Code der Fall ist,

irgendwas kommt rein, dessen Methoden werden nach dem Methodenvertrag geprüft,
internes Verhalten weitgehend egal (wenn mal eine DB zu mocken ist, ist Wissen dazu natürlich unumgänglich),
wenn Ergebnis stimmt dann ist alles ok,
die Implementierung(en) meistern den Test, erfüllen den Vertrag, egal ob jeweils a + b direkt ausrechnet, nur ein festes Dummy-Ergebnis zurückgegegen,
welches zufällig zum Test passt, oder in einer DB nachgeschlagen wird, mit etwas Glück schafft es auch ein Zufallsgenerator…,
egal ob double y sum(double a, double b) eine Klassenmethode oder ein Interface war


einen Test auf ein Interface zu schreiben macht für den Test nur das, was Interface auch sonst leistet: gemeinsamer Code für verschiedene Implementierungen,
die Implementierungen müssen als Klasse nicht für die Testklasse bekannt sein, je nachdem wie der Aufruf funktioniert,
private in bestimmten packages, müssen zum Zeitpunkt des Test-Schreibens noch gar nicht existieren,

das sind die Unterschiede gegenüber Test auf eine Klasse (mit denselben Methoden),
genau wie jede andere Programmier-Verwendung von Interface gegenüber einer Klasse (mit denselben Methoden)

ein Test speziell auf das Interface, unter Bewußtsein dass ein Interface vorliegt, das gibt es eher nicht,


edit: freilich kann eine Klasse A die Methoden von Interface B haben, dazu eigene Methoden,
daneben eine Klasse C die Methoden von Interface B und auch eigene Methoden

dann kann es sinnvoll sein, Tests hinsichtlich Interface B zu schreiben, sie mit A und C auszufühen, und für A und C sonst den kleineren jeweiligen Rest noch einzeln zu testen,
so gesehen mag man „eine Testklasse für Interface B“ haben, mit A und C ausgeführt, und kleinere Testklassen für A und C einzeln,
aber die testet nicht das Interface an sich…

Die Striktheit, mit der hier bestimmte Aussagen gemacht werden, irritiert mich etwas. Man kann sie wohlwollend interpretieren, aber … mit dem Interpretieren hab’ ich’s nicht so.

interface TenProvider { int getTen(); }
class TenProviderA implements TenProvider { int getTen() { return 10; } };
class TenProviderB implements TenProvider { int getTen() { return 10; } };

Das interface gibt vor, wie sich JEDES Objekt von JEDER Implementierung verhalten muss. Warum sollte man da für spezifische Implementierungen getrennte Tests anbieten, wenn jeder Test genau das gleiche macht, außer das “X” bei
this.t = new TenProviderX();
zu ändern?

Nicht mal das. Mittels Parameterized (JUnit API) kannst du deine Tests ans Interface anlehnen und nur an einer Stelle musst du neue Implementierungen einbauen und die Tests nutzen diese sofort mit

@Marco13 & @Clayn
Was hat denn das mit TDD zu tun?
Richtig: Rein gar nix!

Was mich hier irritiert ist dass ganz konkrete Fragen zu TDD gestellt wurden und nun ploetzlich ein „Test laber Thread“ draus wird der nix anderes macht als den TO zu verwirren wie man sieht!

Mal ganz deutlich: Wenn es nix mit TDD zu tun hat, macht das in einem anderen Thread! ::haue

So, jetzt wieder durchschnaufen… diplomatisch ausgedrueckt: Eure Beitraege sind eine Themaverfehlung und tragen nicht dazu bei die gestellten Fragen zu beantworten :wink:

Dass diese „Argumente“ jetzt auf ein ideologisch klingendes und wenig fachliches „Das hat damit nichts zu tun“ zurückfallen, ist etwas bedauerlich. Aber wenn die ursprüngliche, klare Frage „Testet man Interfaces“ für den Threadersteller mit einer Aussage wie ~„Wozu interfaces? Mock’ das doch weg!“ (also: „Das ist doch gar nicht nötig“) beantwortet ist, ist das ja vielleicht OK.

Jaja, ich hab’ TDD nicht richtig verstanden…

Das ist eben das Problem mit Themaverfehlungen: Sie haben nix mit dem eigentlichen Thema zu tun, die Fragen an sich machen keinem Sinn weil der Kontext nicht stimmt.

*** Edit ***

Zum Thema „Testen von Interfaces“:
Falls die Interfaces keinen Code enthalten (default Methods), handelt es sich um Black Box tests, zB. funktionale Tests, Integrations Tests usw.
Auch da „testet“ man nicht das Interface, sondern die Implementierung, die aber eben „unbekannt“ ist aus Sicht des Tests.
D.h. man schriebt seine Tests gegen das Interface, weiss nix von der eigentlichen Implementierung.
Das ist zB. nuetzlich, wenn es mehrere Implementierungen des Interfaces gibt welche die gleiche Spezifikation, das Interface, einhalten sollen.
Dazu kann man parametriesierte Tests nutzen wie von Cryan vorgeschlagen.

Ein anderer Anwendungsfall waeren zB. Integrationstests, die von externee Systemen, welche unzuverlaessig sind, oder langsam, oder „teuer“, in diesem Faellen ersetzt man diese externen Systeme durch andere, simplere/einfachere zu kontrollierenden, die aber immer noch die Spezifikation einhalten sollen.

Nochmal: “Jaja”.

Ich hatte schon damit gerechnet, dass früher oder später versucht wird, TDD irgendwie “zusammenfassend zu rechtfertigen”. Und mir ist klar: Jeder macht’s richtig. Jeder, der etwas macht, und davon überzeugt ist, wird seine Argumente darlegen, und, je nachdem, WIE überzeugt er von bestimmten Dingen ist, entweder sagen oder zumindest denken: “Warum kapiert dieser ignorante Id!ot nicht, wie toll das ist, was ich mache?!”. Die Fragen, die zu der Entwicklung dieses Threads geführt haben, mögen mit Ignoranz, Unverständnis, Kurzsichtigkeit, Uneinsichtigkeit oder schlicht Dummheit meinerseits zu tun haben. Aber es drängt sich mir, wie schon an anderen Stellen angedeutet, der Verdacht auf, dass (ohne das in dieser konkreten Ausprägung jemandem unterstellen zu wollen - das maße ich mir nicht an!!!) TDD eine Ideologie ist, die teilweise blind und (da ist es wieder: ) unreflektiert verfolgt wird, und dass die Gefahr besteht, dass man sich durch einen Haufen grüner Icons in einem Maße in seinem Handeln bestätigt sieht, das in bezug auf übergeordnete Fragen nicht gerechtfertigt ist. Oder, um es etwas plakativer und provokativer zu sagen: Jeder, der der Meinung ist, mit TDD zu entwickeln, und jeder, der der Meinung ist, das, was er da mit TDD entwickelt, sei “gut”, möge mir einen Link auf das entsprechende GitHub-Repo schicken, damit ich ihm sagen kann, was daran nicht gut ist ;P. Und … zum Glück gibt es ja das Internet, wo es für jede noch so absurde Meinung irgendeine Seite gibt, auf der diese Meinung in irgendeiner Weise gerechtfertigt ist. Ganz konkret findet sich die Hauptfrage die (von meiner Seite) zu dieser Entwicklung des Threads geführt hat, zusammen mit einem meine “Meinung” unterstützenden Kommentar schon vorgefertigt unter java - I don’t understand how TDD helps me get a good design if I need a design to start testing it - Software Engineering Stack Exchange

Vielleicht etwas weniger Rhetorik, Polemik und Politik, dachte fuer einen Moment schon im bin in einem ganz anderen Thread/Forum.

Es ist sicher das beste, wenn du gegen TDD auf die Barikaden gehst in einem Thread der urspruenglich von jemandem kam der wissen wollte „wie das geht“, nachdem offensichtlich wurde dass du das Thema missverstanden hattest.

Jaja… :stuck_out_tongue:

Ich finde es unangebracht, wenn die Frage, ob man Interfaces testet, beantwortet wird, indem man etwas sagt, das daran grenzt, die Existenzberechtigung von Interfaces in Frage zu stellen. Genauso, wie man die eine Entwicklung des Threads als Angriff auf TDD ansehen kann, kann man sie als Angriff auf Interface Design ansehen. Oder etwas offensichtlicher: Wenn man im Rahmen seines Designs ein Interface entwirft, muss man die Implementierungen des Interfaces (also ALLE - und in diesem Sinne genau das Interface!) testen. Statt also zu anzudeuten, dass man das Interface weglassen sollte, könnte man auch sagen, dass das eine Sache ist, die schlicht nicht von TDD abgedeckt wird.

Was genau der Grund dafür ist, dass bei jeder Erwähnung von TDD reflexhaft irgendwelche Prinzipien und Vorgehensweisen aufgelistet werden, und gesagt wird, dass kein wahrer Schotte davon abweichen würde, weiß ich nicht. Das bedeutet auch alles nicht, dass ich „gegen TDD auf die Barrikaden“ gehe (ich sehe aber diesen Vorwurf auf der Meta-Ebene als ein Indiz (keinen Beweis!) dafür, dass meine Bedenken berechtigt sind). Und welcher Teil rhetorisch war, ist mir auch nicht klar. Damit, dass ich unterstelle, dass die aufgeworfenen Fragen von denjenigen, die auf Prinzipien pochen, nicht beantwortet werden können, warte ich mal noch ein bißchen :stuck_out_tongue_winking_eye:

Solange niemand widerspricht, wenn ich sage: „Auch bei TDD muss man sich Gedanken über (Schnittstellen-) Design machen“ oder „Interfaces müssen getestet werden (was aber nicht notwendigerweise was mit TDD zu tun hat)“, oder auch allgemeiner „TDD ist nicht alles“, gibt es in bezug auf diesen Punkt eigentlich nichts zu diskutieren - außer eben die Frage, in deren Richtung sich der andere Thread (zu Recht!) entwickelt hat - nämlich wie „TDD“ und „Software-Design“ verheiratet werden können. Dass diese Frage valide ist, und nicht mit „Egal, mach’ einfach deine Tests grün“ beantwortet werden kann, sollte wohl auch klar sein - und wenn das nicht klar ist, dann gehe ich zumindest dagegen auf die Barrikaden.

Die Websuchergebnisse zu „„interface discovery“ tdd“ sind übrigens recht dünn, aber natürlich kann man die Suche etwas relaxieren, um vieeele Seiten zu finden, auf denen „Das Wahre TDD“ erklärt wird.

[quote=Marco13]interface TenProvider { int getTen(); } class TenProviderA implements TenProvider { int getTen() { return 10; } }; class TenProviderB implements TenProvider { int getTen() { return 10; } };Das interface gibt vor, wie sich JEDES Objekt von JEDER Implementierung verhalten muss.[/quote]Ja, aber …

Dein Beispiel ist blöd.
Wenn Du für sowas bei mir Interfaces schreiben würdest würde ich das Projekt verlassen…

Interfaces machen ja nur Sinn, wenn es für deren Implementierung irgendwie Spielraum gibt. ```public interface ActionListener{
void actionPerformed(ActionEvent ae);
}[/quote]
Wie willst Du denn dafür einen Test Schreiben, den alle Implementierungen erfüllen (also außer, dass die Methode ohne Exception abgearbeitet wird.)?

bye
TT

Ich hätte nicht gedacht, dass da so viel Transferleistung erforderlich ist, oder dass man meinen könnte, dass das auf GUI-“Callbacks” (d.h. NICHT-Modell-Klassen) bezogen ist. “GeometricObject” (mit Circle und Rectangle), “Person” (mit Customer und Employee) oder meinetwegen auch “List” (mit ArrayList und LinkedList)… Aber natürlich gibt es da immer Freiheitsgrade.

Ich bin auch kein Testing-Experte, aber das ist doch komplett falsch: Man kann für ein Interface einfach keinen Test schreiben, das ist prinzipiell unmöglich.

Sagen wir mal, ein Interface enthält genau eine Methode int foo(int bar); - wie willst du dieses Interface „testen“, was willst du da in den Unit-Test reinschreiben??

Ist doch überflüssig - das macht der Compiler, genau dafür sind Interfaces ja da. Jeder Test des Interfaces selbst wäre doch nur sinnloser Bloat.

Wenn auf einem Blatt Papier steht, dass sich das Rücklicht einschalten soll wenn man den Rückwärtsgang einlegt - dann kann man dieses Blatt Papier doch in keiner Weise „testen“?

Testen tut man Implementierungen: man schaut nach, ob die korrekt funktionieren, das „Testen von Interfaces“ macht der Compiler. Mann kann über ein Interface
doch nichts wirklich sinnvolles sagen (das ist „richtig“ oder das ist „falsch“), also erst recht nicht testen. Es sei denn, du meinst mit „Contract“ die Semantik der
im Interface beschriebenen Methoden, in dem Fall hast du natürlich recht. Man testet, ob die Implementierungen die korrekte Funktionalität haben.

Kann natürlich auch sein, dass ich mich irre und würde mich da gerne eines besseren belehren lassen - aber das dürfte ja
ein sinnvolles Code-Beispiel für einen Unit-Test eines interfaces erfordern, und ich kann mir gar nicht vorstellen, wie das aussehen könnte.

(Das alles stimmt natürlich nicht mehr für die Default-Methoden in Interfaces, die man sehr wohl testen muss)

[QUOTE=Bleiglanz]Ich bin auch kein Testing-Experte, aber das ist doch komplett falsch: Man kann für ein Interface einfach keinen Test schreiben, das ist prinzipiell unmöglich.

Sagen wir mal, ein Interface enthält genau eine Methode int foo(int bar); - wie willst du dieses Interface “testen”, was willst du da in den Unit-Test reinschreiben??
[/quote]

Wenn so eine Methode in deinem Interface steht, ist das schon komplett falsch. Dort sollte stehen

/**
 * Does foo
 * @param bar must be greater than 0
 * @return will be a value in [0,bar)
 * @throws IllegalArgumentException If bar <=0
 */
int foo(int bar);

Und die Tests könnten dann sein

@Test
public void throwsForZero() {
    TheInterface i = createInstance();
    thrown.expect(IllegalArgumentException.class);
    i.foo(0);
}

@Test
public void resultIsInRange() {
    TheInterface i = createInstance();
    int result = i.foo(24);
    assertTrue(result < 24);
    ...
}
...

wobei die diese suggestive “createInstance”-Methode dann durch Parametrisierung (oder ggf. Vererbung, oder wie auch immer) umgesetzt werden könnte, und wenn jemand dort ein

class TheClass implements TheInterface {
    public int foo(int bar) { return 0; }
}

reinsteckt, kracht’s.

Natürlich testet man die Implementierungen. Aber man testet, ob die Implementierungen das Interface so implementieren, wie es implementiert werden muss. Für 10 solcher Implementierungen 10 Tests zu schreiben, die sich nur in der (angedeuteten!) “createInstance”-Methode unterscheiden, aber ansonsten durch Copy+Paste entstanden sind, würde ja keinen Sinn machen. In diesem Sinne testet man (“gegen”) das Interface.

Zumindest würde ich sowas für sinnvoll halten. Aber natürlich kann das jeder so machen, wie er es für richtig hält.

[quote=Marco13]Wenn so eine Methode in deinem Interface steht, ist das schon komplett falsch. Dort sollte stehen
[/quote]

Ob im javadoc Kommentar (oder wo anders) weitere Specs des Interfaces existieren, ändert nix an der Thematik.

Wie du selbst zugibst, testet dein Test die Implementierung - warum sollte man das ganze dann „Test eines Interfaces“ nennen??

Und wer ist dieser jemand? Jemand der eine fehlerhafte Implementierung schreibt und dieser jemand sollte doch lieber gleich
einen Test für sein eigenes „TheClass“ schreiben. Das von dir beschriebene Vorgehen ist natürlich sinnvoll, wenn es mehrere Implementierungen
gibt (sagen wir beim Strategy-Pattern oder sowas) - man testet die verschiedenen Implementierungen NUR gegen das Interface, das spart Arbeit.

Aber mein Einwand bleibt: die Sprechweise „damit teste ich das Interface“ ist doch einigermaßen seltsam.

Und: Wenn es wirklich mehrere Implementierungen gibt, dann werden die sich ja wohl irgendwie unterscheiden - und müssen dann für diese Unterschiede trotzdem noch weiter getestet werden.