TDD - Testet man Interfaces?

Im Sinne von “keinen Produktivcode schreiben, bevor ein Test dies erzwingt” habe ich den folgenden Test geschrieben:


import org.junit.Test;

public class RefreshObserverTest {

    private class RefreshObserverImplementation implements RefreshObserver {
        private int count = 0;
        @Override
        public void refresh() {
            ++count;
        }
        public int getCount() {
            return count;
        }
    }

    @Test
    public void create() {
        RefreshObserver observer = new RefreshObserverImplementation();
        assertNotNull(observer);
    }

    @Test
    public void refresh() {
        RefreshObserver observer = new RefreshObserverImplementation();
        observer.refresh();
    }

    @Test
    public void refreshAndGettestCount() {
        RefreshObserverImplementation observer = new RefreshObserverImplementation();
        observer.refresh();
        observer.refresh();
        observer.refresh();
        int actual = observer.getCount();
        int expected = 3;
        assertEquals(expected, actual);
    }

}

Ziemlich von oben nach unten, beginnend mit der implementierenden Klasse. Dabei entstand dann auch schon recht schnell das fertige Interface:


    void refresh();

}

Die Sache mit dem Zähler scheint mir etwas umständlich, aber der Test davor, der einfach nur die Methode aufruft, testet ja nicht wirklich etwas, außer dass die Methode in der das Interface implementierenden Klasse da sein muss.

Ist das jetzt Overkill? Sollte man das so machen? Oder gibt es bessere Wege, Interfaces zu testen?

Klar :slight_smile:

Enthalten Interfaces Implementierungen/Code? :wink:
Denn fuer den schreibst man tests in TDD!

Grundsatz:
Schreibe Tests fuer Implementierungen.

Interfaces sind Kandidaten fuer Mocks, wenn sie als „Kollaborateure“, also Abhaenigigkeiten, verwendet werden.

Ansonsten:
Muss man wirklich fuer alles ein Interface haben?
Moderne Mockingframeworks (JMockit, Mockito) mocken auch normale Klassen.

bei konkreten Klassen testen die Mock-Frameworke ja auch durchaus häufig reine interne weitere Methodenaufrufe, auch Anzahl Aufrufe mitgezählt,
da ist es nicht schwer zu verstehen, wie zu dem Test hier gekommen,

Sinn macht er aber absolut nicht, das Interface ist damit in keiner Weise getestet,
ebensowenig spätere zu testende Implementationen davon,
allein eine extra ausgedachte sinnlose Klasse ist getestet, das bring gar nix…


falls das getCount() mit zum Interface gehören würde und später implementierende Klassen genauso getCount()=3 liefern sollen,
dann wäre der Test dafür ok, und nun fertig geschrieben,
Dummy-RefreshObserverImplementation dazu aber unnötig, außer als ‘Test des Tests’ zur einmaligen Ausführung…

die ersten beiden Tests geben auch keinen Sinn.

du erzeugst ein Objekt und testest dann, dass es nicht null ist ? - was soll es denn sonst sein ausser dem Objekt, das hast du ja gerade eben erzeugt

der zweite test ruft einfach eine method auf und macht sonst nix - auch keine Daseinsberechtigung.

Generell ist es schwer zu erkennen, welchen Code deine Tests erzwungen haben, aber so schaut es recht merkwürdig aus.

Zu der generellen Frage hat maki schon geantwortet

Danke, “Schreibe Tests für Implementierungen.” werde ich mir merken.

In diesem Fall schon, da mehrere Klassen diese Schnittstelle implementieren sollen.

@Marco13 Nicht in TDD :wink:

Ansonsten geht das natuerlich, ist aber eher kein isolierter Unit Test mehr (mit Mocks statt echter Abhaengigkeiten), da dann das BlackBox Prinzip ueberwiegt IMHO, gerade weil die Implementierung egal sein sollte wenn man gegen das Interface arbeitet.

TDD ist ein Werkzeug beim dem man Tests nutzt, um den Produktivcode zu Designen, ist eine etwas andere Zielsetzung als klassisches Testen.

Ja, dass das mit TDD nichts zu tun hat, habe ich mir schon gedacht. Ohne die Diskussion jetzt unnötig verbreitern zu wollen: An welchem Punkt bei TDD dann das Design steht („Schnittstellenentwurf“) ist mir nicht ganz klar. Aber ich bin sicher, dass man Argumente nennen kann, warum das gar nicht nötig ist :stuck_out_tongue_winking_eye:

Das nennt sich bei TDD „Interface discovery“ (Google „interface discovery tdd“ ) und passiert sozusagen nebenbei, beschraenkt sich aber nicht nur auf Java Interfaces, sondern ist eben auch auf normale Klassen anwendbar.
D.h. aber nicht dass TDD dazu geeignet ist Schnittstellentests zu entwerfen die unabhaengig von der Implementierung sind wie du es in deinem ersten Beitrag angedeutet hattest :wink:
Bei TDD geht es eben auch darum, die Implementierung zu „formen“, aber eben auch die Schnittstelle von Klassen, bei TDD wird nur die Implementerung getestet, wie gesagt.
Design ist nicht nur Schnittstellen Entwurf, sondern auch der Entwurf der Implementierung.

man kann jede Implementierung den Test durchlaufen lassen…,
ist freilich Blackbox, nur nach Vertrag

List.add(), remove(), size(), Iterator durchgehen

mit TDD muss man für ArrayList, LinkedList, Arrays.asList() (ohne add + remove-Testbestandteile…) usw. alles jeweils kompliziert einzeln neu machen, unendlich Details berücksichtigen,
mit Test nach Interface ein Test für alle, egal was intern passiert…

Nein, macht auch keinen Sinn (in TDD).
Getestet werden Implementierungen in TDD, Interfaces werden gemockt, auch normale Klassen die Abhaengigkeiten sind, werden gemockt.

wieso sollte man ein Interface “mit” testen ?

Man tested code, Implementierungen, das, wo etwas geschieht. Ein interface ist wie ein Politiker, stellt Regeln auf, an denen sich andere halten sollen - mehr nicht. Da ist nix im Interface, was man testet.

Daher werden diese, wie maki schreibt, gemockt (oder wie auch immer “umgangen”)

es ist eh von unterschiedlichen Dingen die Rede bei allen,

Mocken muss man etwa einen DBManager Y in einer zu testenden Komponente X, ziemlich egal ob Y nun Interface ist oder nicht,

zu testen ist dabei aber gerade X, ob X nun wiederum Interface oder anderes ist ist eine ganz andere Frage unabhängig vom Mocken der DB,
insofern passt

[quote=bygones]Da ist nix im Interface, was man testet.

Daher werden diese, wie maki schreibt, gemockt (oder wie auch immer “umgangen”)[/quote]
ziemlich wenig

viele Meinungen, aber man kann ja aus allen etwas mitnehmen

(Off-Topic abgetrennt zu https://forum.byte-welt.net/java-forum/allgemeine-themen/22278-testing-von-interfaces.html )

Beitrag #4 & #5 gehoeren in den TDD Thread :stuck_out_tongue:
Beitrag #12 & #13 aus dem TDD Thread gehoeren hier hin seufz

Räum’ du das auf, wie du es für richtig hältst. Ich halte mich ansonsten hier raus.

Nochmals zur Klarstellung, worum geht es bei TDD?

Es geht darum, wie man Software mithilfe von Tests schreibt, „test getrieben“, die Tests nutzt man um den eigentlichen Prod Code zu formen.

Dabei gibt es einen bestimmten Ablauf, der mehrmals pro Minute durchlaufen wird und bei dem jeder Einzelschritt meist nur ein par Sekunden dauert:
Red → Green → Refactor
… und dann wieder von vorne :slight_smile:

Red: Man schreibt einen einzigen, kurzen Test, wenn dieser fehlschlaegt (auch Compiler Fehler gelten als „fehlschlag“), kommen wir zum naechsten Punkt
Green: Man schreibt nur soviel Prod Code, bis der vorher geschriebene Test nicht mehr fehlschlaegt
Refactor: Man refactored den Prod Code, u.U auch den test code (Make it work, make it clean), hierbei geht es meist um „Optmierungen“ im Sinne von Vereinfachungen des Designs(=Code)

Man will dabei den zu testenden Code soweit wie sinnvoll moeglich von anderem Code isolieren, dazu nutzt man Mocks/Stubs/Fakes.

Waehrend man so seinen Test schreibt merkt man, was der Prod Code so alles koennen sollte um den Test zu erfuellen, manchmal Dinge die nicht direkt im Prod Code der zu testenden Klassen stehen sollten, sondern in anderen Klassen die es oft noch gar nicht gibt, fuehren zur „Interface Discovery“, d.h. soviel wie „ich weiss das meine Prod Klasse die und jene funktionalitaet an eine andere Klasse delegieren sollte, diese andere Klasse gibt es noch nicht, keine Ahnung wie ich das implementieren werde, aber ich weiss jetzt schon diese und jene Methode muss vorhanden sein“.
Das ist der Punkt an dem man ein Mock einsetzt, um diese andere Klasse, oder besser „Rolle“, im jetzigen Test nutzen zu koennen.
Spaeter implementiert man die andere Rolle genauso: Man treibt den Prod Code durch Tests.

Am Anfang steht immer die Frage „Wie teste ich das?“, dann schreibt man ein bisschen Test Code, dann ein bisschen Implementierung, dann refactored man, das zwingt einen dazu Prod Code zu schreiben der einfach testbar ist.
Diese Tests kennen den zu testenden Code Zeile fuer Zeile, kennen die Abhaengigkeiten (andere Klassen/Rollen), denn die Tests muessen ja wissen welche Mocks zu injizieren sind und welche Schnittstellen und welches verhalten diese haben, White Box tests!

Spaetestens jetzt sollte klar sein, dass TDD etwas anderes ist als „Test First“ oder „Wie schreibe ich allgemein Tests fuer meinen Code?“.

[QUOTE=maki]Nein, macht auch keinen Sinn (in TDD).
Getestet werden Implementierungen in TDD, Interfaces werden gemockt, auch normale Klassen die Abhaengigkeiten sind, werden gemockt.[/QUOTE]

Ich glaube, da haben wir aneinander vorbei geredet?

Wenn ich die folgende Struktur habe:

interface I { ... }

class A implements I { ... }

class B implements I { ... }

und Tests für A und B schreibe, dann mocke ich doch dabei bestimmt nicht das Interface I weg. Was ich meinte war, ich schreibe für I keine extra Tests, nur für A und B (um doppelten (Test-)Code zu vermeiden, vermutlich für die Belange des Interfaces I parametrisiert).