[JAVA] Ich möchte irgendwann sogar in Kotlin sein

Es ist üblich geworden, Tests teilweise zu automatisieren. Es scheint, dass einige Leute manchmal "Thread.sleep ()" verwenden, um darauf zu warten, dass der Browser funktioniert. warte eine Minute. Ändert es sich nicht abhängig von der Ruhezeit und der Ausführungsumgebung? Wenn Sie den Vorgang des Wartens auf eine feste Zeit testen möchten, können Sie "Thread.sleep ()" verwenden. Ich denke jedoch, dass die Ruhezeit ad hoc angepasst wird, z. B. "Es hängt vom Netzwerk ab, wie lange es dauern wird, aber es funktioniert nicht, wenn Sie nicht etwa 2 Sekunden warten."

Ich habe früher geschrieben Wie man auf Bildschirmzeichnungen in Selen wartet. Zu diesem Zeitpunkt wollte ich normalerweise {} verwenden, das in ScalaTest in Java enthalten war, daher habe ich es in Java implementiert. Dieses Mal möchte ich es in Kotlin verwenden, nein, ich kann Java-Klassen und -Methoden von Kotlin aus aufrufen, aber ich denke, wenn ich es in Kotlin schreibe, wird es schöner. Ich benutze auch vavr. Lassen Sie uns im Gegensatz zum letzten Mal sehen, wie Sie nach dem Schreiben des grundlegenden Teils konkretisieren können.

Denken Sie an die Schnittstelle

Anstelle der Java / Kotlin-Oberfläche werden wir hier überlegen, welche Art von Formular jährlich einfach zu verwenden sein sollte. Wenn Sie es verwenden, möchten Sie sich frei fühlen, es so zu verwenden.

driver.get(url) 
eventually {
    //Das Öffnen der URL dauert einige Zeit, sodass unmittelbar danach ein Fehler auftritt.
    assert(driver.findElementById("id"))
}

Lassen Sie einfach zu, dass die Klammern vergrößert werden, wenn Sie die Frist und die Wartezeit angeben. Sie sollten ein Standardzeitlimit vorbereiten, normalerweise ohne Klammern, und es nur angeben, wenn Sie es unbedingt angeben müssen.

broswer.login()
eventually ({
    assert(browser.contents().contains(data))
}, Duration.ofSeconds(2), Duration.ofSeconds(1))

schließlich implementiert

Da Kotilin ein Standardargument hat, müssen Sie es nicht verwenden, um eine ähnliche Methode wie Java mit unterschiedlichen Argumenten zu erstellen.

object Eventually {
    val DEFAULT_TIMEOUT = Duration.ofSeconds(10)
    val DEFAULT_INTERVAL = Duration.ofSeconds(3)

    fun <R> eventually(f: () -> R, timeout: Duration = DEFAULT_TIMEOUT, interval: Duration = DEFAULT_INTERVAL): R {
...

Diese Schnittstelle erforderte jedoch sowohl Klammern als auch gewellte Klammern, auch ohne Zeitüberschreitung, wie folgt:

eventually ({
    // ...
})

Ich kann nicht anders, also bereite ich einen anderen vor.

    fun <R> eventually(f: () -> R) = Eventually.eventually(f, DEFAULT_TIMEOUT, DEFAULT_INTERVAL)
    fun <R> eventually(f: () -> R, timeout: Duration = DEFAULT_TIMEOUT, interval: Duration = DEFAULT_INTERVAL): R {
...

Sie können jetzt eine der folgenden Optionen aufrufen:

eventually { ... }
eventually ({ ... }, timeout, interval)
...

Da es sich um Kotlin handelt, können Sie natürlich auch den Argumentnamen angeben.

//Ich möchte nur das Intervall angeben
eventually ({ ... }, interval=Duration.ofSeconds(1))
...

Logikimplementierung

Stellen Sie sich zunächst eine Funktion vor, die "f () verarbeitet und erneut versucht, wenn eine Ausnahme auftritt".

fun <R> tryExecute(f: () -> R):R {
    return try {
        f()
    } catch (t: Throwable) {
        tryExecute(f)
    }
}

Jetzt haben Sie eine Funktion, die für immer wiederholt wird, solange Sie Fehler erhalten. Lassen Sie uns eine Frist setzen, weil es ein Problem ist, wenn es nicht für immer aufhört. Hier verwenden wir java.time.Instant. Ich möchte das Zeitlimit in Dauer angeben, werde es aber später erläutern. Warum sollte ich tailrec hinzufügen, während ich mich zurücklehne?

tailrec fun <R> tryExecute(f: () -> R, until: Instant):R {
    if(now()>until) throw RuntimeException("Ich kann es nicht tun")
    return try {
        f()
    } catch (t: Throwable) {
        Thread.sleep(interval)
        tryExecute(f, until)
    }
}

Wenn f () sofort eine Ausnahme auslöst, wird eine CPU (alle abhängig von der Implementierung von f ()) bis zum Stichtag verwendet, und wenn ein Fehler auftritt, wird eine bestimmte Zeitspanne gewartet. Dieses Mal verwenden wir java.time.Duration, um den Zeitraum darzustellen.

tailrec fun <R> tryExecute(f: () -> R, until: Instant, interval: Duration):R {
    if(now()>until) throw RuntimeException("Ich kann es nicht tun")
    return try {
        f()
    } catch (t: Throwable) {
        Thread.sleep(interval)
        tryExecute(f, until, interval)
    }
}

Nun möchten Sie die Ausnahme, die f () auslöst. Zum Debuggen erforderlich. Übergeben wir die Ausnahme, die bei einer Wiederholung aufgetreten ist, und geben sie als Ursache für die Ausnahme zurück, wenn das Zeitlimit überschritten wird. Die Argumentdefinition wird länger, daher werde ich sie zu einem Zeichen machen.

tailrec fun <R> tryExecute(f: () -> R, u: Instant, i: Duration, t: Throwable):R {
    if(now() > u) throw RuntimeException("Ich kann es nicht tun", t)
    return try {
        f()
    } catch (t: Throwable) {
        Thread.sleep(interval)
        tryExecute(f, u, i, t)
    }
}

Wenn das Zeitlimit abgelaufen ist, erkennt Ursache () die zuletzt abgefangene Ausnahme. Die Logik ist jetzt vollständig.

eventuell () Implementierung

Rufen wir die aktuelle Logik der gerade erstellten Methode auf. Berechnen Sie das Ablaufdatum, indem Sie das durch Duration angegebene Zeitlimit zur aktuellen Zeit hinzufügen.

val start = Instant.now()
val until = start.plusMillis(timeout.toMillis())

tryEvent nimmt die zuletzt abgefangene Ausnahme als Argument, aber zunächst tritt keine Ausnahme auf, und es ist unangenehm, null zu übergeben. Stellen Sie daher sicher, dass Sie Option und Option.none () verwenden. Lass uns gehen. Oh, diese Option verwendet vavr (früher javaslang). Ich werde dies diesmal nicht erklären.


    tailrec fun <R> tryExecute(f: () -> R, u: Instant, i: Duration, t: Option<Throwable>): R {
        if (Instant.now() > u) throw t.getOrElse(TimeoutException())
        return try {
            f()
        } catch (t: Throwable) {
            Thread.sleep(i.toMillis())
            tryExecute(f, u, i, Option.some(t))
        }
    }

tryExecute(f, until, interval, Option.none())

TimeoutException wird jetzt ausgelöst, wenn ein Timeout auftritt, bevor eine Ausnahme auftritt. Wenn Sie diese Ausnahme abfangen, erstellen Sie eine Fehlermeldung zusammen mit der verstrichenen Zeit.

        //Anrufer
        try {
            tryExecute(f, until, interval, Option.none())
        } catch (t: Throwable) {
            throw createException(start, t)
        }

Nachrichtenzusammensetzungsfunktion. Ersetzen konvertiert die Zeichenfolge s in einen Wert, wenn die Karte einen Schlüssel enthält. Es ist rekursiv, aber Sie können es mit map.fold falten.

    fun replace(s: String, m: Map<String, String>): String =
        if (m.isEmpty) s else replace(s.replace(":" + m.head()._1, m.head()._2), m.tail())

    private fun createException(start: Instant, t: Throwable): Throwable {
        val messageMap = HashMap.ofEntries<String, String>(
                Tuple2("time", Duration.between(start, Instant.now()).toString()),
                Tuple2("message", t.message)
        )
        return RuntimeException(replace(MESSAGE_TEMPLATE, messageMap), t)
    }

Dies ist abgeschlossen. Das Ganze ist so.

Eventually.kt


import io.vavr.Tuple2
import io.vavr.collection.HashMap
import io.vavr.collection.Map
import io.vavr.control.Option
import java.time.Duration
import java.time.Instant
import java.util.concurrent.TimeoutException

object Eventually {
    val DEFAULT_TIMEOUT = Duration.ofSeconds(10)
    val DEFAULT_INTERVAL = Duration.ofSeconds(3)

    val MESSAGE_TEMPLATE = "Eventually failed over :time. Last message is:\n:message";
    internal tailrec fun replace(s: String, m: Map<String, String>): String =
        if (m.isEmpty) s 
        else replace(s.replace(":" + m.head()._1, m.head()._2), m.tail())

    private tailrec fun <R> tryExecute(f: () -> R, u: Instant, i: Duration, t: Option<Throwable>): R {
        if (Instant.now() > u) throw t.getOrElse(TimeoutException())
        return try {
            f()
        } catch (t: Throwable) {
            Thread.sleep(i.toMillis())
            tryExecute(f, u, i, Option.some(t))
        }
    }

    private fun createException(start: Instant, t: Throwable): Throwable {
        val messageMap = HashMap.ofEntries<String, String>(
                Tuple2("time", Duration.between(start, Instant.now()).toString()),
                Tuple2("message", t.message)
        )
        return RuntimeException(replace(MESSAGE_TEMPLATE, messageMap), t)
    }

    fun <R> eventually(f: () -> R) = Eventually.eventually(f, DEFAULT_TIMEOUT, DEFAULT_INTERVAL)
    fun <R> eventually(f: () -> R, timeout: Duration = DEFAULT_TIMEOUT, interval: Duration = DEFAULT_INTERVAL): R {
        val start = Instant.now()
        val until = start.plusMillis(timeout.toMillis())
        return try {
            tryExecute(f, until, interval, Option.none())
        } catch (t: Throwable) {
            throw createException(start, t)
        }
    }

Zuerst habe ich versucht, io.vavr.kotlin.Try ohne try {} zu verwenden, und versucht, Event () hat entweder <R, Throwable> zurückgegeben, aber dieses Ich habe es geändert, weil es sauberer war. Die Anzahl der Zeilen hat sich jedoch im Vergleich zu Java-Version nicht wesentlich verringert. Bitte lassen Sie mich wissen, ob es einen besseren Weg gibt.

Prüfung

Tatsächlich werden wir jede Funktion testen, während wir sie implementieren. Wenn Sie beispielsweise replace () schreiben, überprüfen Sie, ob dieser Teil wie beabsichtigt funktioniert. Da Replace privat sein kann, können Sie testReplace () löschen, nachdem Sie bestätigt haben, dass replace () im eventuellen Test aufgerufen wird. Ändern Sie in diesem Fall "interner Spaß ersetzen ()" in "privaten Spaß ersetzen ()". Ich lasse es hier.

EventuallyTest.kt


import <src>.Eventually.eventually
import <src>.Eventually

import io.vavr.Tuple2
import io.vavr.collection.HashMap
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import org.slf4j.LoggerFactory
import java.lang.IllegalArgumentException
import java.time.Duration

typealias e = Tuple2<String, String>

class EventuallyTest {
    private val log = LoggerFactory.getLogger(this.javaClass)

    @Test
    fun testReplace() {
        val r = Eventually.replace("this is :1, :2, :x", HashMap.ofEntries(
                e("1", "changed"),
                e("2", "zzzzz"),
                e("x", "yyyyy")
        ))
        log.info(r)
        assertEquals("this is changed, zzzzz, yyyyy", r)
    }

    @Test
    fun testEventually() {
        val r = eventually {
            log.info("aaa")
            "a"
        }
        assertEquals("a", r)
    }

    @Test
    fun testEventually2Sec() {
        try {
            eventually({
                log.info("aaa")
                throw IllegalArgumentException("x")
            }, timeout = Duration.ofSeconds(2))
        }catch (e: Exception){
            assertEquals("x", e.cause!!.message)
        }
    }
}

KotlinTest

Anscheinend Kotlin Test [eventuell](https://github.com/kotlintest/kotlintest/blob/master/kotlintest-assertions/src/jvmMain/kotlin/io/kotlintest/ Eventuell.kt) scheint zu existieren. Es gibt auch FunSpec usw. und es scheint bequem zu sein, wie ScalaTest zu verwenden. Die Paketbeziehung von KotlinTest selbst ist kompliziert und die Hürde scheint hoch zu sein, aber wenn sie eingeführt werden kann, ist dies auch gut. Es gibt auch eine Erweiterungsmethode für Pfeil.

Recommended Posts

Ich möchte irgendwann sogar in Kotlin sein
Ich möchte die praktischen Funktionen von Clojure in Kotlin nutzen
Selbst in Java möchte ich true mit == 1 && a == 2 && a == 3 ausgeben
Ich möchte eine Parkettdatei auch in Ruby erstellen
Ich möchte @Autowired in Servlet verwenden
Ich möchte eine E-Mail in Java senden.
Selbst in Java möchte ich true mit == 1 && a == 2 && a == 3 ausgeben (PowerMockito Edition)
Ich möchte APP_HOME an Logback in Gradle übergeben
rsync4j - Ich möchte rsync in Java berühren.
Ich möchte den Wert in Ruby erhalten
Sogar in Java möchte ich true mit == 1 && a == 2 && a == 3 ausgeben (Javassist zweite Abkochung)
Selbst in Java möchte ich true mit == 1 && a == 2 && a == 3 (Black Magic) ausgeben.
Ich möchte so etwas wie "cls" in Java machen
Ich möchte eine TraceId in das Protokoll einbetten
Ich möchte auch in Laradock Fischschalen verwenden! !!
Ich möchte ES2015 auch in Java verwenden! → (´ ・ ω ・ `)
Ich möchte eine Funktion in der Rails Console definieren
Ich möchte Bildschirmübergänge mit Kotlin und Java machen!
Ich möchte Schlangenfälle mit Tabellendefinitionen stoppen
Ich möchte in RSpec auf einen GoogleMap-Pin klicken
Ich möchte Zeichen konvertieren ...
[Active Admin] Ich möchte den Umfang der Sammlung angeben, die in select_box angezeigt werden soll
Ich möchte einen relativen Pfad in einer Situation finden, in der Pfad verwendet wird
Ich möchte mit Ruby (ABC177E) eine schnelle Primfaktorisierung durchführen.
Ich möchte eine Liste mit Kotlin und Java erstellen!
Ich möchte eine Funktion mit Kotlin und Java erstellen!
Ich habe versucht, eine Webanwendung voller Fehler mit Kotlin zu implementieren
Ich möchte im gespeicherten Zustand zum selben Bildschirm wechseln
Ich möchte verschiedene Funktionen mit Kotlin und Java implementieren!
Ich möchte die if-else-Anweisung für bedingte Verzweigungen in Java vereinfachen
[CQ Engine] Ich möchte Sammlungen wie Stream oder .Net LINQ auch in Java 7 verarbeiten.
Ich möchte das Flash-Attribut im Frühjahr, auch wenn ich einen Reverse-Proxy festgelegt habe! (TU es nicht)
Ich möchte in der Lage sein, selbst reguläre Ausdrücke zu denken und zu schreiben. ..
Ich möchte mit Kotlin und Java zum vorherigen Bildschirm zurückkehren!
Ich möchte einige Eigenschaften als JSON-Strings in Jackson erhalten!
Ich möchte Geräte in Rails hinzufügen, kann die Installation jedoch nicht bündeln
[Java] Ich möchte mit dem Schlüssel im Objekt eindeutig arbeiten
Ich möchte den Wert von Attribute in Selenium of Ruby ändern
[Android] Ich möchte den Listener über die Schaltfläche in ListView abrufen
Ich möchte eine Webanwendung entwickeln!
[Rails] Ich möchte Daten verschiedener Modelle in einem Formular senden
Ich möchte JSP in Emacs einfacher als die Standardeinstellung schreiben.
Ich möchte ein schönes build.gradle schreiben
Ich möchte doppelte Fehlermeldungen beseitigen
Ich möchte eine ios.android App machen
Ich möchte im Dialogfeld mehrere Elemente mit einem benutzerdefinierten Layout auswählen
Ich möchte DBViewer mit Eclipse 2018-12 verwenden! !!
Ich möchte einen Unit Test schreiben!
(Beschränkt auf Java 7 oder höher) Ich möchte, dass Sie Objekte in Objects.equals vergleichen