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.
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))
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))
...
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.
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
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.
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