Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle

07/07/2016 - 21:19 von Christian H. Kuhn | Report spam
Hallo Gemeinde,

Bevor hier der Löschantrag wegen Inaktivitàt kommt, mach ich doch lieber
nochmal Traffic :-)

Auch mein aktuelles Projekt hat mal wieder Lerneffekte, wo ich sie nicht
erwartet habe. Ziel war, Gradle kennenzulernen und in Jenkins zu
integrieren. Dazu habe ich mir ein gemischtes Java-/Android-Thema
herausgesucht, da kann ich dann auch Android Studio kennenlernen. Mit
Android 2.3.3 hatte ich im Studium mal zu tun, mal schauen, was sich
geàndert hat.

Ich schreibe eine Schachuhr. Die, die es gibt, sind mehrheitlich von
geringer Kenntnis des internationalen Turnierschachs und der FIDE-Regeln
gepràgt, es gibt also sogar mögliche Anwender. Grobstruktur: drei
Pakete. Ein Paket common mit der Funktionalitàt, ein Paket java mit der
Java-Swing-GUI, ein Paket android mit der Android-GUI. Letzteres kommt
spàter und soll erstmal nicht interessieren.

Die Anwendung soll eine elektronische Schachuhr darstellen. Eine
Schachuhr hat zwei Uhren, die von einer vorgegebenen Zeit abwàrts laufen
und bei Erreichen der Null ein optisches Zeichen („Fallblàttchen“)
geben; zwei Knöpfe, die die Uhr auf der Seite anhalten, auf der der
Knopf gedrückt wird, und die Uhr auf der anderen Seite in Gang setzen,
so dass immer nur eine Uhr làuft; einen Knopf zum Stoppen; einen Knopf
zum Zurücksetzen. Verschiedene Funktionen zum Einstellen der Uhr sind im
Menü enthalten. Zur Zeit ist nur eine Bedenkzeitperiode möglich. Erlaubt
sind klassische (Zeit pro Partie) und Fischer-Bedenkzeiten (Zeit pro
Partie, kumulativer Zeitzuschlag pro Zug). Künftige Versionen sollen
auch mehrere Bedenkzeitperioden (mit und ohne Zugzàhler) und
Bronstein-Bedenkzeiten (nicht-kumulativer Zeitzuschlag pro Zug)
enthalten. Eine Light-Version erlaubt nur voreingestellte Bedenkzeiten,
eine Pro-Version erlaubt freie Wahl der Bedenkzeit und ermöglicht
Sonderfunktionen für den Schiedsrichter.

Das Paket common enthàlt zwei Klassen, zwei Interfaces und einen
Aufzàhlungstyp. QChessClock ist die Klasse für die Gesamtfunktion. Sie
implementiert das Interface QChessClockObservable (QChessClockObserver
wird von den GUI implementiert). Der Aufzàhlungstyp QChessClockState
wird in einer privaten Variablen benutzt, um den Zustand (làuft,
angehalten, nicht gestartet) festzuhalten. Zwei weitere private
Variablen sind vom Typ QChessTimer. Die öffentliche Schnittstelle
besteht aus Methoden, die das Drücken der Knöpfe und die Menübefehle
abbilden, und den Methoden zum Registrieren und Benachrichtigen der
Observer. Außer notifyObservers() gibt es keine öffentliche Funktion zum
Abfragen des Zustands von privaten Variablen.

QChessTimer stellt die einzelne Uhr dar. Die „öffentliche“ Schnittstelle
ist nur paketsichtbar, da ein direkter Zugriff auf die einzelnen Uhren
nur über die Hauptklasse QChessClock erfolgen soll. Verschiedene
Konstruktoren erlauben das initiale Setzen der Bedenkzeit. start() und
stop() dienen zum Starten und Anhalten der Uhr. isRunning() gibt den
Status (làuft, angehalten) an, isFlagFallen() zeigt ein gefallenes
Blàttchen an. unsetFlagFallen() setzt das Fallblàttchen zurück. Und
schließlich gibt getRemainingSeconds() die verbleibende Zeit in Sekunden an.

Wer den Quellcode sehen will: https://www.qno.de/gitweb/

Das Coden war jetzt tatsàchlich das kleinere Problem, und Gradle ist
zwar anders als Maven, aber gut bedienbar. Probleme tauchen beim Testen
auf. Ich will ja TDD anwenden. Also erst die Tests und dann den Code
schreiben. Hat mit JUnit4 bei QChessTimer auch ganz gut funktioniert.

Bei QChessClock habe ich nicht den Schimmer einer Vorstellung, wie ich
die Klasse testen soll. Es gibt keine Getter-Funktionen, mit denen sich
was überprüfen ließe. Ich kann also testen, dass das Testobjekt nach
Konstruktor ungleich null ist, sonst nichts. Eine Änderung der
Schnittstelle nur zu Testzwecken kommt selbstverstàndlich nicht in
Frage. Bleibt eigentlich nur, dass die Testklasse QChessClockObserver
implementiert und auf die Benachrichtigungen des Observable wartet. Der
Weg ist gangbar, ich habe ihn aber noch in keinem Buch gefunden. Daher
vermute ich, dass es da eleganteres gibt?

In noch extremerem Umfang gilt das für die Java-GUI. Die GUI erzeugt ein
QChessClock-Objekt und registriert sich dort als Observer. Sie stellt
die erwàhnten Knöpfe und Menüs bereit, die auf die öffentlichen Methoden
der Schachuhr zugreifen. Öffentliche Methoden sind außer dem Konstruktor
und actionPerformed() noch die verschiedenen update()-Funktionen, über
die das Observable seine Zustandsànderungen mitteilt. Auch hier habe ich
praktisch nichts, was ich mit JUnit testen könnte, und andere Tests der
Klasse kenne ich nicht. Auch für den Integrationstest des gesamten
Systems scheint JUnit das falsche Mittel zu sein.

In der Folge ist entsprechend die Testabdeckung, die von JaCoCo
ermittelt wird, entsprechend niedrig. Wenn andere Tests eingesetzt
werden, werden die von JaCoCo erkannt? Oder muss man Klassen dann aus
der Testabdeckungsanalyse exkludieren?

Und schließlich die alles entscheidende Frage: Kann mir das jemand hier
beantworten? Oedr denke ich zu theoretisch?

TIA
QNo
 

Lesen sie die antworten

#1 Peter
07/07/2016 - 22:37 | Warnen spam
Am 07.07.2016 um 21:19 schrieb Christian H. Kuhn:
Hallo Gemeinde,


[viel Text]

Dein Vorgehen ist falsch.
Du mußt ein Projekt so aufsetzen, daß es von Anfang an testbar ist.
Die ganz hart gesottenen schreiben erst die Testklassen, und danach die
Implementierung.
Nicht ganz so radikal aber wirksam:
Die Implementierung und die Testklassen gemeinsam entwerfen.
Der Sinn dieses Vorgehens besteht genau darin, daß das Projekt von
Anfang an testbar ist, und nicht im nachhinein festgestellt wird, daß
man das Design nicht vernünftig testen kann.

Package-private Schnittstellen kann man testen, indem du das
Test-Package mit der Implementierung identisch benennst;
Du hast also im Projekt zwei Ordner mit Sourcen:
src/myPack/MyClass
und
test/myPack/TestMyClass

Und besorg dir ein Werkzeug, welches die Testabdeckung mißt; so kannst
du leicht feststellen, wo noch Tests fehlen.

Ähnliche fragen