Path: csiph.com!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail From: "Christian H. Kuhn" Newsgroups: de.comp.lang.java Subject: Re: JUnit Test von JButton: Action wird nicht erkannt Date: Tue, 19 Jul 2016 14:59:50 +0200 Lines: 200 Message-ID: References: Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Trace: individual.net 7mRZr5e3VKqqWr8YL9fwRQWhj1Na4zFFDQEfJ4MzllYWXdMcc= Cancel-Lock: sha1:6ZNLzEOIHlQOS8gfoPolUVPEdWE= User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Thunderbird/45.2.0 In-Reply-To: Xref: csiph.com de.comp.lang.java:13019 Version 0.9.2 Am 15.07.2016 um 22:04 schrieb Patrick Roemer: > Responding to Christian H. Kuhn: > interface TimeSource { > long getTimeInNanoSecs(); > } Den Gedanken habe ich aufgegriffen und mal implementiert. Die TimeSource darf sich dabei aussuchen, welche Zeiteinheit sie verwendet; es muss nur ein ganzzahliger Bruchteil einer Sekunde sein. public interface QTimeSource { long getFactorToSeconds(); // erlaubt versch. time units long getNow(); } public class QSystemMilliTime implements QTimeSource { private static final long MILLIS_PER_SECOND = 1000; @Override public final long getFactorToSeconds() { return MILLIS_PER_SECOND; } @Override public final long getNow() { return System.currentTimeMillis(); } } private static class TestTimeSource implements QTimeSource { private static final long SECONDS_TO_SECONDS = 1; private long now; @Override public long getFactorToSeconds() { return SECONDS_TO_SECONDS; } @Override public long getNow() { synchronized (this) { return now; } } public void setNow(final long _now) { synchronized (this) { now = _now; } } } Dazu habe ich Stefan Rams Timer-Konzept im großen und ganzen übernommen und mit synchronize(this) (hoffentlich?) thread safe gemacht. Der Timer arbeitet jetzt mit abstrakten Zeiten. Zur Sicherheit müsste im Konstruktor die startTime noch auf ein adäquates now gesetzt werden, falls von start(long) auch Werte <= 0 kommen könnten. public final class QCountdownTimer { private transient long remainingTime; private transient long additionalFischerTime; private transient long startTime; private transient long stopTime; private transient boolean running; QCountdownTimer(final long _timeOfReflection) { this(_timeOfReflection, 0); } QCountdownTimer(final long _timeOfReflection, final long _additionalFischerTime) { running = false; additionalFischerTime = _additionalFischerTime; remainingTime = _timeOfReflection + additionalFischerTime; } /* default */ long getRemainingTime(final long _now) { // Parameter has no effect if !running long actualRemaningTime; synchronized (this) { if (running && _now > startTime) { actualRemaningTime = remainingTime - _now + startTime; } else { actualRemaningTime = remainingTime; } return actualRemaningTime; } } /* default */ void start(final long _startTime) { synchronized (this) { if (!running && _startTime >= stopTime) { running = true; startTime = _startTime; } } } /* default */ void stop(final long _stopTime) { synchronized (this) { if (running && _stopTime >= startTime) { running = false; stopTime = _stopTime; remainingTime = remainingTime - (stopTime - startTime) + additionalFischerTime; } } } /* default */ boolean isRunning() { synchronized (this) { return running; } } } QChessClock kann jetzt im Konstruktor eine QTimeSource mitbekommen, ansonsten nimmt sie QSystemMilliTime. > ...und für Tests dann sowas: Genau. > Das ist aber nur die halbe Miete. Man würde dann auch den konkreten > j.u.Timer in der Kernklasse wegabstrahieren wollen. Siehe den langen Subthread „Countdown Timer Design“ mit vielen Beiträgen von Stefan Ram. Dem bin ich in der ersten Stufe gefolgt: QCountdownTimer weiss nichts von Zeitquellen und hat auch keinen TimerTask mehr. Der konkrete Zeitbegriff und die Überprüfung auf Zeitablauf erfolgt im Verwender. In einem Punkt stimme ich mit Stefan nicht überein. Er hat auch die Schachuhr von TimeSource und TimerTask befreit. In der einfachen Blitzschachuhr, die er benutzt, ist das ok. Da wird bei Blättchenfall einfach nur eine Anzeige gesetzt und gut. Abweichend vom sauberen TDD, siehe Beiträge von Wanja Gayk im Thread „Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle“, weiss ich aber jetzt schon, dass in der nächsten Ausbaustufe beim Blättchenfall zusätzliche Funktionalität dazukommt. Die ist in der Begrifflichkeit des Kano-Modells kein Begeisterungs- oder auch nur Leistungs-, sondern Basis-Merkmal und wird definitiv im ersten Release enthalten sein. Also darf ich auch ohne Test in die Richtung planen ;-) Es geht um mehrere Bedenkzeitperioden. Bislang implementiert sind Bedenkzeitmodelle mit einer Periode. Ist die Zeit für diese Periode abgelaufen, ist die Partie im Prinzip vorbei. Die Zeitüberschreitung wird angezeigt, mehr Funktionalität ist nicht nötig. Das wird in der Praxis bei sogenannten Blitz- und Rapid-Partien angewandt und betrifft typischerweise kurze Bedenkzeiten von 1 bis 90 min pro Spieler und Partie, mit Zeitzuschlägen von 1 bis 20 s. In „langen“, „ernsten“, „Turnier-“ Partien werden üblicherweise zwei oder mehr Zeitperioden benutzt. In den unteren Amateurklassen ist sehr verbreitet: 2 h für die ersten 40 Züge, danach ein Zuschlag von einer oder einer halben Stunde. Die Bundesliga spielte von 2009 bis 2014 mit 100 min für die ersten 40 Züge, 50 min für die nächsten 20 Züge, 15 min für den Rest, 30s akkumulierender Zuschlag pro Zug. Die letzte Zeitperiode wird gehandhabt wie bisher: Bei Ablauf Anzeige und gut. Die Zeitperioden davor machen mehr Arbeit. Bei Ablauf der Periode (in der Regel durch Bedenkzeitüberschreitung, gelegentlich auch durch Erreichen der Zügezahl, wofür noch ein Zugzähler zu implementieren ist) wird die Zeitüberschreitung beim jeweiligen Spieler so angezeigt, dass sie über einen längeren Zeitraum festgestellt werden kann. Gleichzeitig wird die erlaubte Bedenkzeit der nächsten Periode bei beiden Spielern zur aktuellen Bedenkzeit addiert. Ich bin ja der Ansicht, dass diese Umstellung der verbleibenden Bedenkzeiten ein Wissen über Implementationsdetails der Schachuhr verlangt. Das ist vor anderen Klassen zu verbergen und gehört damit in die Schachuhr. Auch die regelmäßige Überprüfung auf Zeitüberschreitung darf nicht anderen Klassen überlassen werden. Sie ist von der Schachuhr selbst zu leisten; entsprechende Ereignisse sind an Oberflächen zu senden. Damit benötige ich einen TimerTask in der Schachuhr, ein ScheduledThreadPoolExecutor wäre ne Nummer zu dick. Der TimerTask überprüft in sinnvollen Abständen auf ZÜ. Bei Anzeigengenauigkeit von 1s wäre ein Takt von 1s zu langsam; der durchschnittliche Fehler von einer halben Sekunde ist beim Blitzen bereits relevant. Der Millisekunden-Takt der Standard-Zeitquelle würde sehr genau anzeigen, aber eine nicht mehr wahrnehmbare Genauigkeit produzieren. Der Mensch kann Bilder in Folgen von 24/s wahrnehmen, Sicherheitsfaktor 2, runden, sind wir bei 1/50 s oder 20 ms. Und wenn dieser Thread schon mal existiert, kann er auch veränderte Zeiten an die GUI mitteilen. > Über SwingUtilities#invokeLater(). Mit den beiden invoke bin ich auch am Kämpfen. Beide sorgen auf dem EDT für eine klare Abfolge von Events. Nur invokeAndWait() sorgt aber auch für eine happens-before-Beziehung zwischen ausgeführten Befehlen auf dem aufrufenden Thread und dem EDT. Für manche Tests nicht unwichtig, die darauf warten müssen, dass das EDT-Ereignis auch ausgeführt ist, bevor der Test funktionieren kann. Dafür wirft invokeAndWait() eine Exception. In normalem Code kann die problemlos abgefangen und verarbeitet werden; in Runnables und actionPerformed muss man entweder eine try-catch mit leerem Catch-Block machen, was alle statischen Codechecker anmeckern, oder man macht im Catch-Block so Dinge wie final Thread thread = Thread.currentThread(); thread.getUncaughtExceptionHandler().uncaughtException(thread, e); die auch keinen Schönheitspreis gewinnen. lg QNo